cue: implement embedding and close structs

A few small tweaks to the spec are made.

This also implements the non-recursive version
of embedding. Relaxing it to the recursive
version can be done later if needed.

Issue #40

Change-Id: I69dc7e361059baae6bad2d04666e5047edcbf865
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2872
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast.go b/cue/ast.go
index 3a5db12..4c2dcf7 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -230,17 +230,43 @@
 			v1.doc = v.doc
 		}
 		ret = obj
-		for _, e := range n.Elts {
+		for i, e := range n.Elts {
 			switch x := e.(type) {
+			case *ast.Ellipsis:
+				if i != len(n.Elts)-1 {
+					return v1.walk(x.Type) // Generate an error
+				}
+				f := v.ctx().label("_", true)
+				sig := &params{}
+				sig.add(f, &basicType{newNode(x), stringKind})
+				template := &lambdaExpr{newNode(x), sig, &top{newNode(x)}}
+				v1.object.addTemplate(v.ctx(), token.NoPos, template)
+
 			case *ast.EmbedDecl:
-				// Only allowed at top-level.
-				return v1.errf(x, "emitting values is only allowed at top level")
+				e := v1.walk(x.Expr)
+				if isBottom(e) {
+					return e
+				}
+				if e.kind()&structKind == 0 {
+					return v1.errf(x, "can only embed structs (found %v)", e.kind())
+				}
+				ret = mkBin(v1.ctx(), x.Pos(), opUnifyUnchecked, ret, e)
+				obj = &structLit{}
+				v1.object = obj
+				ret = mkBin(v1.ctx(), x.Pos(), opUnifyUnchecked, ret, obj)
+
 			case *ast.Field, *ast.Alias:
 				v1.walk(e)
+
 			case *ast.ComprehensionDecl:
 				v1.walk(x)
 			}
 		}
+		if v.ctx().inDefinition > 0 {
+			// For embeddings this is handled in binOp, in which case the
+			// isClosed bit is cleared if a template is introduced.
+			obj.isClosed = obj.template == nil
+		}
 		if passDoc {
 			v.doc = v1.doc // signal usage of document back to parent.
 		}
@@ -271,10 +297,14 @@
 		ret = list
 
 	case *ast.Ellipsis:
-		return v.errf(n, "ellipsis (...) only allowed at end of list")
+		return v.errf(n, "ellipsis (...) only allowed at end of list or struct")
 
 	case *ast.ComprehensionDecl:
-		yielder := &yield{baseValue: newExpr(n.Field.Value)}
+		yielder := &yield{
+			baseValue: newExpr(n.Field.Value),
+			opt:       n.Field.Optional != token.NoPos,
+			def:       n.Field.Token == token.ISA,
+		}
 		fc := &fieldComprehension{
 			baseValue: newDecl(n),
 			clauses:   wrapClauses(v, yielder, n.Clauses),
@@ -334,6 +364,12 @@
 
 	case *ast.Field:
 		opt := n.Optional != token.NoPos
+		isDef := n.Token == token.ISA
+		if isDef {
+			ctx := v.ctx()
+			ctx.inDefinition++
+			defer func() { ctx.inDefinition-- }()
+		}
 		switch x := n.Label.(type) {
 		case *ast.Interpolation:
 			v.sel = "?"
@@ -345,9 +381,13 @@
 			yielder.key = v.walk(x)
 			yielder.value = v.walk(n.Value)
 			yielder.opt = opt
+			yielder.def = isDef
 			v.object.comprehensions = append(v.object.comprehensions, fc)
 
 		case *ast.TemplateLabel:
+			if isDef {
+				v.errf(x, "map element type cannot be a definition")
+			}
 			v.sel = "*"
 			f := v.label(x.Ident.Name, true)
 
@@ -358,11 +398,7 @@
 			v.setScope(n, template)
 			template.value = v.walk(n.Value)
 
-			if v.object.template == nil {
-				v.object.template = template
-			} else {
-				v.object.template = mkBin(v.ctx(), token.NoPos, opUnify, v.object.template, template)
-			}
+			v.object.addTemplate(v.ctx(), token.NoPos, template)
 
 		case *ast.BasicLit, *ast.Ident:
 			if internal.DropOptional && opt {
@@ -392,7 +428,7 @@
 					}
 				}
 				val := v.walk(n.Value)
-				v.object.insertValue(v.ctx(), f, opt, val, attrs, v.doc)
+				v.object.insertValue(v.ctx(), f, opt, isDef, val, attrs, v.doc)
 				v.doc = leftOverDoc
 			}
 
@@ -445,6 +481,8 @@
 
 			case "len":
 				return lenBuiltin
+			case "close":
+				return closeBuiltin
 			case "and":
 				return andBuiltin
 			case "or":
diff --git a/cue/binop.go b/cue/binop.go
index b52150c..99ad043 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -183,6 +183,8 @@
 	panic("unreachable: special-cased")
 }
 
+// add adds to a unification. Note that the value cannot be a struct and thus
+// there is no need to distinguish between checked and unchecked unification.
 func (x *unification) add(ctx *context, src source, v evaluated) evaluated {
 	for progress := true; progress; {
 		progress = false
@@ -218,7 +220,8 @@
 }
 
 func (x *unification) binOp(ctx *context, src source, op op, other evaluated) evaluated {
-	if op == opUnify {
+	if _, isUnify := op.unifyType(); isUnify {
+		// Cannot be checked unification.
 		u := &unification{baseValue: baseValue{src}}
 		u.values = append(u.values, x.values...)
 		if y, ok := other.(*unification); ok {
@@ -237,7 +240,7 @@
 
 func (x *top) binOp(ctx *context, src source, op op, other evaluated) evaluated {
 	switch op {
-	case opUnify:
+	case opUnify, opUnifyUnchecked:
 		return other
 	}
 	src = mkBin(ctx, src.Pos(), op, x, other)
@@ -328,7 +331,7 @@
 
 	newSrc := binSrc(src.Pos(), op, x, other)
 	switch op {
-	case opUnify:
+	case opUnify, opUnifyUnchecked:
 		k, _, msg := matchBinOpKind(opUnify, x.kind(), other.kind())
 		if k == bottomKind {
 			return ctx.mkErr(src, msg, opUnify, ctx.str(x), ctx.str(other), x.kind(), other.kind())
@@ -495,7 +498,7 @@
 func (x *customValidator) binOp(ctx *context, src source, op op, other evaluated) evaluated {
 	newSrc := binSrc(src.Pos(), op, x, other)
 	switch op {
-	case opUnify:
+	case opUnify, opUnifyUnchecked:
 		k, _, msg := matchBinOpKind(opUnify, x.kind(), other.kind())
 		if k == bottomKind {
 			return ctx.mkErr(src, msg, op, ctx.str(x), ctx.str(other), x.kind(), other.kind())
@@ -582,7 +585,7 @@
 
 func (x *structLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
 	y, ok := other.(*structLit)
-	_, isUnify := op.unifyType()
+	unchecked, isUnify := op.unifyType()
 	if !ok || !isUnify {
 		return ctx.mkIncompatible(src, op, x, other)
 	}
@@ -599,6 +602,7 @@
 		binSrc(src.Pos(), op, x, other), // baseValue
 		x.emit,                          // emit
 		nil,                             // template
+		x.isClosed || y.isClosed,        // isClosed
 		nil,                             // comprehensions
 		arcs,                            // arcs
 		nil,                             // attributes
@@ -629,6 +633,8 @@
 	if t != nil {
 		obj.template = ctx.copy(t)
 	}
+	// If unifying with a closed struct that does not have a template,
+	// we need to apply the template to all elements.
 
 	sz := len(x.comprehensions) + len(y.comprehensions)
 	obj.comprehensions = make([]*fieldComprehension, sz)
@@ -640,15 +646,38 @@
 	}
 
 	for _, a := range x.arcs {
+		found := false
+		for _, b := range y.arcs {
+			if a.feature == b.feature {
+				found = true
+				break
+			}
+		}
+		if !unchecked && !found && !y.allows(a.feature) {
+			if a.optional {
+				continue
+			}
+			return ctx.mkErr(src, y, "field %q not allowed in closed struct",
+				ctx.labelStr(a.feature))
+		}
 		cp := ctx.copy(a.v)
 		obj.arcs = append(obj.arcs,
-			arc{a.feature, a.optional, cp, nil, a.attrs, a.docs})
+			arc{a.feature, a.optional, a.definition, cp, nil, a.attrs, a.docs})
 	}
 outer:
 	for _, a := range y.arcs {
 		v := ctx.copy(a.v)
+		found := false
 		for i, b := range obj.arcs {
 			if a.feature == b.feature {
+				found = true
+				if a.definition != b.definition {
+					src := binSrc(x.Pos(), op, a.v, b.v)
+					return ctx.mkErr(src, "field %q declared as definition and regular field",
+						ctx.labelStr(a.feature))
+				}
+				// TODO: using opUnify here disables recursive opening in
+				// embedding. Change to op enable it.
 				v = mkBin(ctx, src.Pos(), opUnify, b.v, v)
 				obj.arcs[i].v = v
 				obj.arcs[i].cache = nil
@@ -662,11 +691,22 @@
 				continue outer
 			}
 		}
+		if !unchecked && !found && !x.allows(a.feature) {
+			if a.optional {
+				continue
+			}
+			return ctx.mkErr(a.v, x, "field %q not allowed in closed struct",
+				ctx.labelStr(a.feature))
+		}
 		a.setValue(v)
 		obj.arcs = append(obj.arcs, a)
 	}
 	sort.Stable(obj)
 
+	if unchecked && obj.template != nil {
+		obj.isClosed = false
+	}
+
 	return obj
 }
 
diff --git a/cue/builtin.go b/cue/builtin.go
index dc73586..c6fc328 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -91,7 +91,7 @@
 		for _, a := range pkg.arcs {
 			// Discard option status and attributes at top level.
 			// TODO: filter on capitalized fields?
-			obj.insertValue(ctx, a.feature, false, a.v, nil, a.docs)
+			obj.insertValue(ctx, a.feature, false, false, a.v, nil, a.docs)
 		}
 	}
 
@@ -138,6 +138,20 @@
 	},
 }
 
+var closeBuiltin = &builtin{
+	Name:   "close",
+	Params: []kind{structKind},
+	Result: structKind,
+	Func: func(c *callCtxt) {
+		s, ok := c.args[0].(*structLit)
+		if !ok {
+			c.ret = errors.Newf(c.args[0].Pos(), "struct argument must be concrete")
+			return
+		}
+		c.ret = s.close()
+	},
+}
+
 var andBuiltin = &builtin{
 	Name:   "and",
 	Params: []kind{listKind},
diff --git a/cue/context.go b/cue/context.go
index 9fa7323..29c50a1 100644
--- a/cue/context.go
+++ b/cue/context.go
@@ -31,8 +31,9 @@
 	constraints []*binaryExpr
 	evalStack   []bottom
 
-	inSum    int
-	cycleErr bool
+	inDefinition int
+	inSum        int
+	cycleErr     bool
 
 	noManifest bool
 
diff --git a/cue/copy.go b/cue/copy.go
index 5a6594b..a28eaa2 100644
--- a/cue/copy.go
+++ b/cue/copy.go
@@ -31,7 +31,7 @@
 	case *structLit:
 		arcs := make(arcs, len(x.arcs))
 
-		obj := &structLit{x.baseValue, nil, nil, nil, arcs, nil}
+		obj := &structLit{x.baseValue, nil, nil, x.isClosed, nil, arcs, nil}
 
 		defer ctx.pushForwards(x, obj).popForwards()
 
diff --git a/cue/debug.go b/cue/debug.go
index d7bfede..1db9eff 100644
--- a/cue/debug.go
+++ b/cue/debug.go
@@ -267,8 +267,19 @@
 		if p.showNodeRef {
 			p.writef("<%s>", p.ctx.ref(x))
 		}
-		writef("{")
-		if x.template != nil {
+		if x.isClosed {
+			write("C")
+		}
+		write("{")
+		topDefault := false
+		switch {
+		case x.template != nil:
+			lambda, ok := x.template.(*lambdaExpr)
+			if ok {
+				if _, topDefault = lambda.value.(*top); topDefault {
+					break
+				}
+			}
 			write("<>: ")
 			p.str(x.template)
 			write(", ")
@@ -280,6 +291,12 @@
 				p.write(", ")
 			}
 		}
+		if topDefault && !x.isClosed {
+			if len(x.arcs) > 0 {
+				p.write(", ")
+			}
+			p.write("...")
+		}
 		write("}")
 
 	case []arc:
@@ -302,7 +319,11 @@
 		if x.optional {
 			p.write("?")
 		}
-		p.write(": ")
+		if x.definition {
+			p.write(" :: ")
+		} else {
+			p.write(": ")
+		}
 		p.str(n)
 		if x.attrs != nil {
 			for _, a := range x.attrs.attr {
diff --git a/cue/eval.go b/cue/eval.go
index 3a99033..0a36b21 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -67,7 +67,7 @@
 		}
 		if n.val() == nil {
 			field := ctx.labelStr(x.feature)
-			if _, ok := sc.(*structLit); ok {
+			if st, ok := sc.(*structLit); ok && !st.isClosed {
 				return ctx.mkErr(x, codeIncomplete, "undefined field %q", field)
 			}
 			//	m.foo undefined (type map[string]bool has no field or method foo)
@@ -105,7 +105,10 @@
 				return ctx.mkErr(x, index, codeIncomplete, "field %q is optional", s)
 			}
 			if n.val() == nil {
-				return ctx.mkErr(x, index, codeIncomplete, "undefined field %q", s)
+				if !v.isClosed {
+					return ctx.mkErr(x, index, codeIncomplete, "undefined field %q", s)
+				}
+				return ctx.mkErr(x, index, "undefined field %q", s)
 			}
 			return n.cache
 		}
@@ -252,7 +255,7 @@
 func (x *listComprehension) evalPartial(ctx *context) evaluated {
 	s := &structLit{baseValue: x.baseValue}
 	list := &list{baseValue: x.baseValue, elem: s}
-	result := x.clauses.yield(ctx, func(k, v evaluated, _ bool) *bottom {
+	result := x.clauses.yield(ctx, func(k, v evaluated, _, _ bool) *bottom {
 		if !k.kind().isAnyOf(intKind) {
 			return ctx.mkErr(k, "key must be of type int")
 		}
diff --git a/cue/export.go b/cue/export.go
index 5f3611f..f834300 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -33,7 +33,7 @@
 }
 
 func export(ctx *context, v value, m options) (n ast.Node, imports []string) {
-	e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}}
+	e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}, false}
 	top, ok := v.evalPartial(ctx).(*structLit)
 	if ok {
 		top, err := top.expandFields(ctx)
@@ -94,6 +94,7 @@
 	stack   []remap
 	top     map[label]bool        // label to alias or ""
 	imports map[string]importInfo // pkg path to info
+	inDef   bool                  // TODO(recclose):use count instead
 }
 
 type importInfo struct {
@@ -202,6 +203,30 @@
 	return short
 }
 
+func hasTemplate(s *ast.StructLit) bool {
+	for _, e := range s.Elts {
+		if f, ok := e.(*ast.Field); ok {
+			if _, ok := f.Label.(*ast.TemplateLabel); ok {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func (p *exporter) closeOrOpen(s *ast.StructLit, isClosed bool) ast.Expr {
+	if isClosed && !p.inDef {
+		return &ast.CallExpr{
+			Fun:  ast.NewIdent("close"),
+			Args: []ast.Expr{s},
+		}
+	}
+	if !isClosed && p.inDef && !hasTemplate(s) {
+		s.Elts = append(s.Elts, &ast.Ellipsis{})
+	}
+	return s
+}
+
 func (p *exporter) expr(v value) ast.Expr {
 	// TODO: use the raw expression for convert incomplete errors downstream
 	// as well.
@@ -210,7 +235,7 @@
 		x := p.ctx.manifest(e)
 		if isIncomplete(x) {
 			if isBottom(e) {
-				p = &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports}
+				p = &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports, p.inDef}
 				return p.expr(v)
 			}
 			v = e
@@ -318,6 +343,12 @@
 		return &ast.UnaryExpr{Op: opMap[x.op], X: p.expr(x.x)}
 
 	case *binaryExpr:
+		// opUnifyUnchecked: represented as embedding. The two arguments must
+		// be structs.
+		if x.op == opUnifyUnchecked {
+			s := &ast.StructLit{}
+			return p.closeOrOpen(s, p.embedding(s, x))
+		}
 		return &ast.BinaryExpr{
 			X:  p.expr(x.left),
 			Op: opMap[x.op], Y: p.expr(x.right),
@@ -358,10 +389,29 @@
 		return bin
 
 	case *structLit:
-		expr, err := p.structure(x)
+		st, err := p.structure(x, !x.isClosed)
 		if err != nil {
 			return p.expr(err)
 		}
+		expr := p.closeOrOpen(st, x.isClosed)
+		switch {
+		case x.isClosed && x.template != nil:
+			l, ok := x.template.evalPartial(p.ctx).(*lambdaExpr)
+			if !ok {
+				break
+			}
+			if _, ok := l.value.(*top); ok {
+				break
+			}
+			expr = &ast.BinaryExpr{X: expr, Op: token.AND, Y: &ast.StructLit{
+				Elts: []ast.Decl{&ast.Field{
+					Label: &ast.TemplateLabel{
+						Ident: p.identifier(l.params.arcs[0].feature),
+					},
+					Value: p.expr(l.value),
+				}},
+			}}
+		}
 		return expr
 
 	case *fieldComprehension:
@@ -520,7 +570,7 @@
 	}
 }
 
-func (p *exporter) structure(x *structLit) (ret *ast.StructLit, err *bottom) {
+func (p *exporter) structure(x *structLit, addTempl bool) (ret *ast.StructLit, err *bottom) {
 	obj := &ast.StructLit{}
 	if doEval(p.mode) {
 		x, err = x.expandFields(p.ctx)
@@ -539,9 +589,13 @@
 	if x.emit != nil {
 		obj.Elts = append(obj.Elts, &ast.EmbedDecl{Expr: p.expr(x.emit)})
 	}
-	if !doEval(p.mode) && x.template != nil {
+	switch {
+	case !doEval(p.mode) && x.template != nil && addTempl:
 		l, ok := x.template.evalPartial(p.ctx).(*lambdaExpr)
 		if ok {
+			if _, ok := l.value.(*top); ok && !x.isClosed {
+				break
+			}
 			obj.Elts = append(obj.Elts, &ast.Field{
 				Label: &ast.TemplateLabel{
 					Ident: p.identifier(l.params.arcs[0].feature),
@@ -566,20 +620,26 @@
 			}
 			f.Optional = token.NoSpace.Pos()
 		}
+		if a.definition {
+			f.Token = token.ISA
+		}
 		if a.feature&hidden != 0 && p.mode.concrete && p.mode.omitHidden {
 			continue
 		}
+		oldInDef := p.inDef
+		p.inDef = a.definition || p.inDef
 		if !doEval(p.mode) {
 			f.Value = p.expr(a.v)
 		} else {
 			e := x.at(p.ctx, i)
 			if v := p.ctx.manifest(e); isIncomplete(v) && !p.mode.concrete && isBottom(e) {
-				p := &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports}
+				p := &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports, p.inDef}
 				f.Value = p.expr(a.v)
 			} else {
 				f.Value = p.expr(e)
 			}
 		}
+		p.inDef = oldInDef
 		if a.attrs != nil && !p.mode.omitAttrs {
 			for _, at := range a.attrs.attr {
 				f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
@@ -624,6 +684,31 @@
 	return obj, nil
 }
 
+func (p *exporter) embedding(s *ast.StructLit, n value) (closed bool) {
+	switch x := n.(type) {
+	case *structLit:
+		st, err := p.structure(x, true)
+		if err != nil {
+			n = err
+			break
+		}
+		s.Elts = append(s.Elts, st.Elts...)
+		return x.isClosed
+
+	case *binaryExpr:
+		if x.op != opUnifyUnchecked {
+			// should not happen
+			s.Elts = append(s.Elts, &ast.EmbedDecl{Expr: p.expr(x)})
+			return false
+		}
+		leftClosed := p.embedding(s, x.left)
+		rightClosed := p.embedding(s, x.right)
+		return leftClosed || rightClosed
+	}
+	s.Elts = append(s.Elts, &ast.EmbedDecl{Expr: p.expr(n)})
+	return false
+}
+
 // quote quotes the given string.
 func quote(str string, quote byte) string {
 	if strings.IndexByte(str, '\n') < 0 {
diff --git a/cue/export_test.go b/cue/export_test.go
index ce87d91..760a135 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -70,6 +70,8 @@
 				f: string
 			}`),
 	}, {
+		// Here the failed lookups are not considered permanent
+		// failures, as the structs are open.
 		in: `{ a: { b: 2.0, s: "abc" }, b: a.b, c: a.c, d: a["d"], e: a.t[2:3] }`,
 		out: unindent(`
 			{
@@ -83,6 +85,45 @@
 				e: a.t[2:3]
 			}`),
 	}, {
+		// Here the failed lookups are permanent failures as the structs are
+		// closed.
+		in: `{ a :: { b: 2.0, s: "abc" }, b: a.b, c: a.c, d: a["d"], e: a.t[2:3] }`,
+		out: unindent(`
+			{
+				a :: {
+					b: 2.0
+					s: "abc"
+				}
+				b: 2.0
+				c: _|_ /* undefined field "c" */
+				d: _|_ /* undefined field "d" */
+				e: _|_ /* undefined field "t" */
+			}`),
+	}, {
+		// a closed struct with template restrictions is exported as a
+		// conjunction of two structs.
+		in: `{
+			A :: { b: int }
+			a: A & { <_>: <10 }
+			B :: a
+		}`,
+		out: unindent(`
+		{
+			A :: {
+				b: int
+			}
+			a: close({
+				b: <10
+			}) & {
+				<_>: <10
+			}
+			B :: {
+				b: <10
+			} & {
+				<_>: <10
+			}
+		}`),
+	}, {
 		in: `{
 			a: 5*[int]
 			a: [1, 2, ...]
@@ -298,6 +339,87 @@
 			}
 		}`),
 	}, {
+		raw: true,
+		in: `{
+			emb :: {
+				a: 1
+
+				sub: {
+					f: 3
+				}
+			}
+			def :: {
+				emb
+
+				b: 2
+			}
+			f :: { a: 10 }
+			e :: {
+				f
+
+				b: int
+				<_>: <100
+			}
+		}`,
+		out: unindent(`
+		{
+			emb :: {
+				a: 1
+				sub f: 3
+			}
+			f :: {
+				a: 10
+			}
+			def :: {
+				emb
+
+				b: 2
+			}
+			e :: {
+				f
+		
+				<_>: <100
+				b:   int
+			}
+		}`),
+	}, {
+		raw:  true,
+		eval: true,
+		in: `{
+				reg: { foo: 1, bar: { baz: 3 } }
+				def :: {
+					a: 1
+
+					sub: reg
+				}
+				val: def
+			}`,
+		out: unindent(`
+		{
+			reg: {
+				foo: 1
+				bar baz: 3
+			}
+			def :: {
+				a: 1
+				sub: {
+					foo: 1
+					bar: {
+						baz: 3
+						...
+					}
+					...
+				}
+			}
+			val: close({
+				a: 1
+				sub: {
+					foo: 1
+					bar baz: 3
+				}
+			})
+		}`),
+	}, {
 		raw:  true,
 		eval: true,
 		in: `{
diff --git a/cue/kind.go b/cue/kind.go
index 1eec58a..a2a7235 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -196,7 +196,7 @@
 				return k, true, ""
 			}
 			return bottomKind, false, msg
-		case opUnify:
+		case opUnify, opUnifyUnchecked:
 			if a&nullKind != 0 {
 				return k, false, ""
 			}
@@ -256,7 +256,7 @@
 	}
 	// a and b have overlapping types.
 	switch op {
-	case opUnify:
+	case opUnify, opUnifyUnchecked:
 		// Increase likelihood of unification succeeding on first try.
 		return u, swap, ""
 
diff --git a/cue/op.go b/cue/op.go
index e13f965..5706c2d 100644
--- a/cue/op.go
+++ b/cue/op.go
@@ -134,6 +134,9 @@
 }
 
 func (op op) unifyType() (unchecked, ok bool) {
+	if op == opUnifyUnchecked {
+		return true, true
+	}
 	return false, op == opUnify
 }
 
@@ -143,6 +146,7 @@
 	opUnknown op = iota
 
 	opUnify
+	opUnifyUnchecked
 	opDisjunction
 
 	opLand
@@ -174,8 +178,11 @@
 var opStrings = []string{
 	opUnknown: "??",
 
-	opUnify:       "&",
-	opDisjunction: "|",
+	opUnify: "&",
+	// opUnifyUnchecked is internal only. Syntactically this is
+	// represented as embedding.
+	opUnifyUnchecked: "&!",
+	opDisjunction:    "|",
 
 	opLand: "&&",
 	opLor:  "||",
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index f14e91d..736bbc3 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -549,8 +549,9 @@
 
 func TestResolve(t *testing.T) {
 	testCases := []testCase{{
-		in:  `a: { <_>: _ }`,
-		out: `<0>{a: <1>{<>: <2>(_: string)->_, }}`,
+		desc: "convert _ to top",
+		in:   `a: { <_>: _ }`,
+		out:  `<0>{a: <1>{...}}`,
 	}, {
 		in: `
 			a: b.c.d
@@ -611,7 +612,7 @@
 			`c: <3>{foo: 1 @bar() @baz(1) @foo()}, ` +
 			`e: _|_((<4>.a & <5>{foo: 1 @foo(other)}):conflicting attributes for key "foo")}`,
 	}, {
-		desc: "optional fields",
+		desc: "optional field unification",
 		in: `
 			a: { foo?: string }
 			b: { foo: "foo" }
@@ -629,6 +630,16 @@
 			`g1: 1, ` +
 			`g2?: 2}`,
 	}, {
+		desc: "optional field resolves to incomplete",
+		in: `
+		r: {
+			a?: 3
+			b: a
+			c: r["a"]
+		}
+	`,
+		out: `<0>{r: <1>{a?: 3, b: <2>.a, c: <2>["a"]}}`,
+	}, {
 		desc: "bounds",
 		in: `
 			i1: >1 & 5
@@ -1069,6 +1080,188 @@
 			`,
 		out: `<0>{a: <1>{c: 5, d: 15}, t: <2>{c: number, d: (<3>.c * 3)}, b: <4>{c: 7, d: 21}, ti: <5>{c: int, d: (<6>.c * 3)}}`,
 	}, {
+		desc: "definitions",
+		in: `
+			Foo :: {
+				field: int
+				recursive: {
+					field: string
+				}
+			}
+
+			Foo1 :: { field: int }
+			Foo1 :: { field2: string }
+
+			foo: Foo
+			foo: { feild: 2 }
+
+			foo1: Foo
+			foo1: {
+				field: 2
+				recursive: {
+					feild: 2 // Not caught as per spec. TODO: change?
+				}
+			}
+
+			Bar :: {
+				field: int
+				<A>:   int
+			}
+			bar: Bar
+			bar: { feild: 2 }
+
+			Mixed :: string
+			Mixed: string
+
+			mixedRec: { Mixed :: string }
+			mixedRec: { Mixed: string }
+			`,
+		out: `<0>{` +
+			`Foo :: <1>C{field: int, recursive: <2>C{field: string}}, ` +
+			`Foo1 :: _|_((<3>C{field: int} & <4>C{field2: string}):field "field" not allowed in closed struct), ` +
+			`foo: _|_(2:field "feild" not allowed in closed struct), ` +
+			`foo1: <5>C{field: 2, recursive: _|_(2:field "feild" not allowed in closed struct)}, ` +
+			`Bar :: <6>{<>: <7>(A: string)->int, field: int}, ` +
+			`bar: <8>{<>: <9>(A: string)->int, field: int, feild: 2}, ` +
+			`Mixed: _|_(field "Mixed" declared as definition and regular field), ` +
+			`mixedRec: _|_(field "Mixed" declared as definition and regular field)}`,
+	}, {
+		desc: "definitions with oneofs",
+		in: `
+			Foo :: {
+				field: int
+
+				{ a: 1 } |
+				{ b: 2 }
+			}
+
+			foo: Foo
+			foo: { a: 2 }
+
+			bar: Foo
+			bar: { c: 2 }
+
+			baz: Foo
+			baz: { b: 2 }
+			`,
+		out: `<0>{` +
+			`Foo :: (<1>C{field: int, a: 1} | <2>C{field: int, b: 2}), ` +
+			`foo: _|_((<3>.Foo & <4>{a: 2}):empty disjunction: C{field: int, a: (1 & 2)}), ` +
+			`bar: _|_((<3>.Foo & <5>{c: 2}):empty disjunction: field "c" not allowed in closed struct), ` +
+			`baz: <6>C{field: int, b: 2}}`,
+	}, {
+		desc: "definitions with embedding",
+		in: `
+		E :: {
+			a: { b: int }
+		}
+
+		S :: {
+			E
+			a: { c: int }
+			b: 3
+		}
+
+		// error: literal struct is closed before unify
+		e1 :: S & { a c: 4 }
+		// no such issue here
+		v1: S & { a c: 4 }
+
+		// adding a field to a nested struct that is closed.
+		e2: S & { a d: 4 }
+		`,
+		out: `<0>{` +
+			`E :: <1>C{a: <2>C{b: int}}, ` +
+			`S :: <3>C{a: _|_((<4>C{b: int} & <5>C{c: int}):field "b" not allowed in closed struct), b: 3}, ` +
+			`e1 :: _|_((<6>.S & <7>C{a: <8>C{c: 4}}):field "b" not allowed in closed struct), ` +
+			`v1: <9>C{a: _|_((<10>C{b: int} & <11>C{c: int}):field "b" not allowed in closed struct), b: 3}, ` +
+			`e2: <12>C{a: _|_((<13>C{b: int} & <14>C{c: int}):field "b" not allowed in closed struct), b: 3}}`,
+	}, {
+		desc: "closing structs",
+		in: `
+		op: {x: int}             // {x: int}
+		ot: {x: int, ...}        // {x: int, ...}
+		cp: close({x: int})      // closed({x: int})
+		ct: close({x: int, ...}) // {x: int, ...}
+
+		opot: op & ot  // {x: int, ...}
+		otop: ot & op  // {x: int, ...}
+		opcp: op & cp  // closed({x: int})
+		cpop: cp & op  // closed({x: int})
+		opct: op & ct  // {x: int, ...}
+		ctop: ct & op  // {x: int, ...}
+		otcp: ot & cp  // closed({x: int})
+		cpot: cp & ot  // closed({x: int})
+		otct: ot & ct  // {x: int, ...}
+		ctot: ct & ot  // {x: int, ...}
+		cpct: cp & ct  // closed({x: int})
+		ctcp: ct & cp  // closed({x: int})
+		ctct: ct & ct  // {x: int, ...}
+		`,
+		out: `<0>{` +
+			`op: <1>{x: int}, ` +
+			`ot: <2>{x: int, ...}, ` +
+			`cp: <3>C{x: int}, ` +
+			`ct: <4>{x: int, ...}, ` +
+			`opot: <5>{x: int, ...}, ` +
+			`otop: <6>{x: int, ...}, ` +
+			`opcp: <7>C{x: int}, ` +
+			`cpop: <8>C{x: int}, ` +
+			`opct: <9>{x: int, ...}, ` +
+			`ctop: <10>{x: int, ...}, ` +
+			`otcp: <11>C{x: int}, ` +
+			`cpot: <12>C{x: int}, ` +
+			`otct: <13>{x: int, ...}, ` +
+			`ctot: <14>{x: int, ...}, ` +
+			`cpct: <15>C{x: int}, ` +
+			`ctcp: <16>C{x: int}, ` +
+			`ctct: <17>{x: int, ...}}`,
+	}, {
+		desc: "closing with failed optional",
+		in: `
+		k1 :: {a: int, b?: int} & {a: int} // closed({a: int})
+		k2 :: {a: int} & {a: int, b?: int} // closed({a: int})
+
+		o1: {a?: 3} & {a?: 4} // {a?: _|_}
+
+		// Optional fields with error values can be elimintated when closing
+		o2 :: {a?: 3} & {a?: 4} // close({})
+
+		d1 :: {a?: 2, b: 4} | {a?: 3, c: 5}
+		v1: d1 & {a?: 3, b: 4}  // close({b: 4})
+		`,
+		out: `<0>{` +
+			`k1 :: <1>C{a: int}, ` +
+			`k2 :: <2>C{a: int}, ` +
+			`o1: <3>{a?: _|_((3 & 4):conflicting values 3 and 4)}, ` +
+			`o2 :: <4>C{a?: _|_((3 & 4):conflicting values 3 and 4)}, ` +
+			`d1 :: (<5>C{a?: 2, b: 4} | <6>C{a?: 3, c: 5}), ` +
+			`v1: <7>C{a?: _|_((2 & 3):conflicting values 2 and 3), b: 4}}`,
+	}, {
+		desc: "closing with comprehensions",
+		in: `
+		A :: {f1: int, f2: int}
+
+		// Comprehension fields cannot be added like any other.
+		a: A & { "\(k)": v for k, v in {f3 : int}}
+
+		// A closed struct may not generate comprehension values it does not
+		// define explicitly.
+		B :: {"\(k)": v for k, v in {f1: int}}
+
+		// To fix this, add all allowed fields.
+		C :: {f1: _, "\(k)": v for k, v in {f1: int}}
+
+		// Or like this.
+		D :: {"\(k)": v for k, v in {f1: int}, ...}
+		`,
+		out: `<0>{` +
+			`A :: <1>C{f1: int, f2: int}, ` +
+			`a: _|_(int:field "f3" not allowed in closed struct), ` +
+			`B :: _|_(int:field "f1" not allowed in closed struct), ` +
+			`C :: <2>C{f1: int}, ` +
+			`D :: <3>{f1: int, ...}}`,
+	}, {
 		desc: "reference to root",
 		in: `
 			a: { b: int }
@@ -1564,6 +1757,15 @@
 				a: 7080 | int`,
 		out: `<0>{a: _|_((8000.9 & (int | int)):conflicting values 8000.9 and int (mismatched types float and int))}`, // TODO: fix repetition
 	}, {
+		desc: "conflicts in optional fields are okay ",
+		in: `
+			d: {a: 1, b?: 3} | {a: 2}
+
+			// the following conjunction should not eliminate any disjuncts
+			c: d & {b?:4}
+		`,
+		out: `<0>{d: (<1>{a: 1, b?: 3} | <2>{a: 2}), c: (<3>{a: 1, b?: (3 & 4)} | <4>{a: 2, b?: 4})}`,
+	}, {
 		desc: "resolve all disjunctions",
 		in: `
 			service <Name>: {
@@ -1622,8 +1824,12 @@
 				a: 3
 				a: 3 if a > 1
 			}
+			d: {
+				a: int
+				a: 3 if a > 1
+			}
 		`,
-		out: `<0>{b: true, c: <1>{a: 3}, a: "foo"}`,
+		out: `<0>{b: true, c: <1>{a: 3}, a: "foo", d: <2>{a: int if (<2>.a > 1) yield ("a"): 3}}`,
 	}, {
 		desc: "referencing field in field comprehension",
 		in: `
diff --git a/cue/rewrite.go b/cue/rewrite.go
index c135a15..06171e0 100644
--- a/cue/rewrite.go
+++ b/cue/rewrite.go
@@ -237,7 +237,7 @@
 	if key == x.key && value == x.value {
 		return x
 	}
-	return &yield{x.baseValue, x.opt, key, value}
+	return &yield{x.baseValue, x.opt, x.def, key, value}
 }
 
 func (x *guard) rewrite(ctx *context, fn rewriteFunc) value {
diff --git a/cue/rewrite_test.go b/cue/rewrite_test.go
index 4a6acc5..b9738c1 100644
--- a/cue/rewrite_test.go
+++ b/cue/rewrite_test.go
@@ -75,7 +75,7 @@
 			t = v
 		}
 		emit := testResolve(ctx, x.emit, m)
-		obj := &structLit{x.baseValue, emit, t, nil, arcs, nil}
+		obj := &structLit{x.baseValue, emit, t, x.isClosed, nil, arcs, nil}
 		return obj
 	case *list:
 		elm := rewriteRec(ctx, x.elem, x.elem, m).(*structLit)
diff --git a/cue/types.go b/cue/types.go
index c95af87..dddef8e 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1078,12 +1078,13 @@
 		}
 		arcs = arcs[:k]
 		obj = &structLit{
-			obj.baseValue,
-			obj.emit,
-			obj.template,
-			nil,
-			arcs,
-			nil,
+			obj.baseValue, // baseValue
+			obj.emit,      // emit
+			obj.template,  // template
+			obj.isClosed,  // isClosed
+			nil,           // comprehensions
+			arcs,          // arcs
+			nil,           // attributes
 		}
 	}
 	return structValue{ctx, v.path, obj}, nil
diff --git a/cue/value.go b/cue/value.go
index 8d58770..ed49e71 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -624,8 +624,24 @@
 	baseValue
 
 	// TODO(perf): separate out these infrequent values to save space.
-	emit           value // currently only supported at top level.
-	template       value
+	emit value // currently only supported at top level.
+	// TODO: make this a list of templates and don't unify until templates are
+	// applied. This allows generalization of having different constraints
+	// for different field sets. This could also be used to mark closedness:
+	// use [string]: _ for fully open. This could be a sentinel value.
+	// For now we use a boolean for closedness.
+
+	// NOTE: must be conjunction of lists.
+	// For lists originating from closed structs,
+	// there must be at least one match.
+	// templates [][]value
+	// catch_all: value
+
+	// template must evaluated to a lambda and is applied to all concrete
+	// values in the struct, whether it be open or closed.
+	template value
+	isClosed bool
+
 	comprehensions []*fieldComprehension
 
 	// TODO: consider hoisting the template arc to its own value.
@@ -633,6 +649,18 @@
 	expanded evaluated
 }
 
+func (x *structLit) addTemplate(ctx *context, pos token.Pos, t value) {
+	if x.template == nil {
+		x.template = t
+	} else {
+		x.template = mkBin(ctx, pos, opUnify, x.template, t)
+	}
+}
+
+func (x *structLit) allows(f label) bool {
+	return !x.isClosed
+}
+
 func newStruct(src source) *structLit {
 	return &structLit{baseValue: src.base()}
 }
@@ -645,6 +673,16 @@
 func (x *structLit) Less(i, j int) bool { return x.arcs[i].feature < x.arcs[j].feature }
 func (x *structLit) Swap(i, j int)      { x.arcs[i], x.arcs[j] = x.arcs[j], x.arcs[i] }
 
+func (x *structLit) close() *structLit {
+	if x.template != nil {
+		return x // there is nothing to close as it is already fully defined.
+	}
+
+	newS := *x
+	newS.isClosed = true
+	return &newS
+}
+
 // lookup returns the node for the given label f, if present, or nil otherwise.
 func (x *structLit) lookup(ctx *context, f label) arc {
 	x, err := x.expandFields(ctx)
@@ -674,11 +712,22 @@
 }
 
 func (x *structLit) at(ctx *context, i int) evaluated {
+	// TODO: limit visibility of definitions:
+	// Approach:
+	// - add package identifier to arc (label)
+	// - assume ctx is unique for a package
+	// - record package identifier in context
+	// - if arc is a definition, check IsExported and verify the package if not.
+	//
+	// The same approach could be valid for looking up package-level identifiers.
+	// - detect somehow aht root nodes are.
+	//
+	// Allow import of CUE files. These cannot have a package clause.
+
 	x, err := x.expandFields(ctx)
 	if err != nil {
 		return err
 	}
-
 	// if x.emit != nil && isBottom(x.emit) {
 	// 	return x.emit.(evaluated)
 	// }
@@ -755,7 +804,7 @@
 	newArcs := []arc{}
 
 	for _, c := range comprehensions {
-		result := c.clauses.yield(ctx, func(k, v evaluated, opt bool) *bottom {
+		result := c.clauses.yield(ctx, func(k, v evaluated, opt, def bool) *bottom {
 			if !k.kind().isAnyOf(stringKind) {
 				return ctx.mkErr(k, "key must be of type string")
 			}
@@ -777,9 +826,10 @@
 				}
 			}
 			newArcs = append(newArcs, arc{
-				feature:  f,
-				optional: opt,
-				v:        v,
+				feature:    f,
+				optional:   opt,
+				definition: def,
+				v:          v,
 			})
 			return nil
 		})
@@ -803,6 +853,7 @@
 		x.baseValue, // baseValue
 		emit,        // emit
 		template,    // template
+		false,       // isClosed
 		nil,         // comprehensions
 		newArcs,     // arcs
 		nil,         // attributes
@@ -843,8 +894,10 @@
 // however, may have both. In this case, the value must ultimately evaluate
 // to a node, which will then be merged with the existing one.
 type arc struct {
-	feature  label
-	optional bool
+	feature    label
+	optional   bool
+	definition bool // field is a definition
+
 	// TODO: add index to preserve approximate order within a struct and use
 	// topological sort to compute new struct order when unifying. This could
 	// also be achieved by not sorting labels on features and doing
@@ -905,19 +958,27 @@
 }
 
 // insertValue is used during initialization but never during evaluation.
-func (x *structLit) insertValue(ctx *context, f label, optional bool, value value, a *attributes, docs *docNode) {
+func (x *structLit) insertValue(ctx *context, f label, optional, isDef bool, value value, a *attributes, docs *docNode) {
 	for i, p := range x.arcs {
 		if f != p.feature {
 			continue
 		}
-		x.arcs[i].v = mkBin(ctx, token.NoPos, opUnify, p.v, value)
-		// TODO: should we warn if there is a mixed mode of optional and non
-		// optional fields at this point?
 		x.arcs[i].optional = x.arcs[i].optional && optional
 		x.arcs[i].docs = mergeDocs(x.arcs[i].docs, docs)
+		x.arcs[i].v = mkBin(ctx, token.NoPos, opUnify, p.v, value)
+		if isDef != p.definition {
+			src := binSrc(token.NoPos, opUnify, p.v, value)
+			x.arcs[i].v = ctx.mkErr(src,
+				"field %q declared as definition and regular field",
+				ctx.labelStr(f))
+			isDef = false
+		}
+		x.arcs[i].definition = isDef
+		// TODO: should we warn if there is a mixed mode of optional and non
+		// optional fields at this point?
 		return
 	}
-	x.arcs = append(x.arcs, arc{f, optional, value, nil, a, docs})
+	x.arcs = append(x.arcs, arc{f, optional, isDef, value, nil, a, docs})
 	sort.Stable(x)
 }
 
@@ -1322,7 +1383,7 @@
 	return topKind | nonGround
 }
 
-type yieldFunc func(k, v evaluated, optional bool) *bottom
+type yieldFunc func(k, v evaluated, optional, definition bool) *bottom
 
 type yielder interface {
 	value
@@ -1332,6 +1393,7 @@
 type yield struct {
 	baseValue
 	opt   bool
+	def   bool
 	key   value
 	value value
 }
@@ -1352,7 +1414,7 @@
 	if isBottom(v) {
 		return v
 	}
-	if err := fn(k, v, x.opt); err != nil {
+	if err := fn(k, v, x.opt, x.def); err != nil {
 		return err
 	}
 	return nil
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index eb53b34..48393ff 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -976,6 +976,22 @@
 in their respective field values need to be replaced with references to `c`.
 The result of a unification is bottom (`_|_`) if any of its required
 fields evaluates to bottom, recursively.
+<!--NOTE: About bottom values for optional fields being okay.
+
+The proposition ¬P is a close cousin of P → ⊥ and is often used
+as an approximation to avoid the issues of using not.
+Bottom (⊥) is also frequently used to mean undefined. This makes sense.
+Consider `{a?: 2} & {a?: 3}`.
+Both structs say `a` is optional; in other words, it may be omitted.
+So we can still get a valid result by omitting `a`, even in
+case of a conflict.
+
+Granted, this definition may lead to confusing results, especially in
+definitions, when tightening an optional field leads to unintentionally
+discarding it.
+It could be a role of vet checkers to identify such cases (and suggest users
+to explicitly use `_|_` to discard a field, for instance).
+-->
 
 Syntactically, the labels of optional fields are followed by a
 question mark `?`.
@@ -998,6 +1014,8 @@
 labels match the expression.
 -->
 A Bind label binds an identifier to the label name scoped to the field value.
+It also makes all possible labels an optional field set to the
+associated field value.
 The token `...` is a shorthand for `<_>: _`.
 <!-- NOTE: if we allow ...Expr, as in list, it would mean something different. -->
 
@@ -1103,12 +1121,11 @@
 #### Closed structs
 
 By default, structs are open to adding fields.
-One could say that an optional field `f` with value top (`_`) is defined for any
-unspecified field.
+Instances of an open struct `p` may contain fields not defined in `p`.
 A _closed struct_ `c` is a struct whose instances may not have fields
 not defined in `c`.
 Closing a struct is equivalent to adding an optional field with value `_|_`
-for any undefined field.
+for all undefined fields.
 
 Note that fields created with field comprehensions are not considered
 defined fields.
@@ -1150,9 +1167,19 @@
 #### Embedding
 
 A struct may contain an _embedded value_, an Operand used
-as a field declaration.
+as a declaration, which must evaluate to a struct.
+An embedded value of type struct is unified with the struct in which it is
+embedded, but disregarding the restrictions imposed by closed structs
+for its top-level fields.
+<!--TODO: consider relaxing it to the below.
 An embedded value of type struct is unified with the struct in which it is
 embedded, but disregarding the restrictions imposed by closed structs.
+
+Note that in the above definition we cannot say that the fields of the
+embedded struct are added: references within these fields referring to
+the embedded struct should be rewired to reference the new struct.
+This would not be the case with  per-field definition.
+-->
 A struct resulting from such a unification is closed if either of the involved
 structs were closed.
 
@@ -1166,7 +1193,8 @@
 It is illegal to have a normal field and a definition with the same name
 within the same struct.
 Literal structs that are part of a definition's value are implicitly closed.
-An ellipsis `...` in such literal structs keeps them open.
+An ellipsis `...` in such literal structs keeps them open,
+as it defines `_` for all labels.
 
 
 ```