cue: allow export of schema and other options

- allow Syntax to fail if data is not concrete
- don't resolve references in non-final mode.

The API is currently no longer sufficient. Most notably
it seems like the Syntax method should return an error.
But this is not entirely clear and is best addressed as
part of the evaluator rewrite.

Issue #257 // allows cue def command
Issue #91  // allows cue def command
Issue #288 // allows builtins to mark input as incomplete

Change-Id: I86a7a63a1c730a1ab2591dc848c8e3aee5cc6459
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5083
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/eval.go b/cmd/cue/cmd/eval.go
index e68f614..a63a7d6 100644
--- a/cmd/cue/cmd/eval.go
+++ b/cmd/cue/cmd/eval.go
@@ -110,6 +110,8 @@
 			fmt.Fprintf(w, "\n// %s\n", inst.Dir)
 		}
 		syn := []cue.Option{
+			cue.Final(), // for backwards compatibility
+			cue.Definitions(true),
 			cue.Attributes(flagAttributes.Bool(cmd)),
 			cue.Optional(flagAll.Bool(cmd) || flagOptional.Bool(cmd)),
 		}
diff --git a/cmd/cue/cmd/testdata/script/eval_rec.txt b/cmd/cue/cmd/testdata/script/eval_rec.txt
index 5522df3..5d3a94c 100644
--- a/cmd/cue/cmd/testdata/script/eval_rec.txt
+++ b/cmd/cue/cmd/testdata/script/eval_rec.txt
@@ -15,21 +15,15 @@
 }
 -- expect-stdout --
 Foo: {
-    foo?: {
-        bar: string
-        baz: bar + "2"
-    }
-    bar: string
-    baz: bar + "2"
+    foo?: Foo
+    bar:  string
+    baz:  bar + "2"
 }
 foo: {
     foo: {
-        foo?: {
-            bar: string
-            baz: bar + "2"
-        }
-        bar: "barNested"
-        baz: "barNested2"
+        foo?: Foo
+        bar:  "barNested"
+        baz:  "barNested2"
     }
     bar: "barParent"
     baz: "barParent2"
diff --git a/cue/builtin.go b/cue/builtin.go
index a7479d7..3f8c7b9 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -299,8 +299,12 @@
 			ret = err.err
 			ret = ctx.mkErr(src, x, ret, "error in call to %s: %v", x.name(ctx), err)
 		default:
-			// TODO: store the underlying error explicitly
-			ret = ctx.mkErr(src, x, "error in call to %s: %v", x.name(ctx), err)
+			if call.err == internal.ErrIncomplete {
+				ret = ctx.mkErr(src, codeIncomplete, "incomplete value")
+			} else {
+				// TODO: store the underlying error explicitly
+				ret = ctx.mkErr(src, x, "error in call to %s: %v", x.name(ctx), err)
+			}
 		}
 	}()
 	x.Func(&call)
diff --git a/cue/export.go b/cue/export.go
index 5ab732f..80d2255 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -35,7 +35,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{}, false}
+	e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}, false, nil}
 	top, ok := v.evalPartial(ctx).(*structLit)
 	if ok {
 		top, err := top.expandFields(ctx)
@@ -94,6 +94,12 @@
 	top     map[label]bool        // label to alias or ""
 	imports map[string]importInfo // pkg path to info
 	inDef   bool                  // TODO(recclose):use count instead
+
+	incomplete []source
+}
+
+func (p *exporter) addIncomplete(v value) {
+	// TODO: process incomplete values
 }
 
 type importInfo struct {
@@ -260,7 +266,7 @@
 func (p *exporter) closeOrOpen(s *ast.StructLit, isClosed bool) ast.Expr {
 	// Note, there is no point in printing close if we are dropping optional
 	// fields, as by this the meaning of close will change anyway.
-	if !p.showOptional() {
+	if !p.showOptional() || p.mode.final {
 		return s
 	}
 	if isClosed && !p.inDef && !hasTemplate(s) {
@@ -277,9 +283,20 @@
 	case *numLit, *stringLit, *bytesLit, *nullLit, *boolLit:
 		return true
 	case *list:
+		if p.mode.final || !all {
+			return true
+		}
+		if x.isOpen() {
+			return false
+		}
+		for i := range x.elem.arcs {
+			if !p.isComplete(x.at(p.ctx, i), all) {
+				return false
+			}
+		}
 		return true
 	case *structLit:
-		return !all
+		return !all && p.mode.final
 	case *bottom:
 		return !isIncomplete(x)
 	case *closeIfStruct:
@@ -299,16 +316,39 @@
 }
 
 func (p *exporter) recExpr(v value, e evaluated, optional bool) ast.Expr {
-	m := p.ctx.manifest(e)
-	if optional || (!p.isComplete(m, false) && (!p.mode.concrete)) {
+	var m evaluated
+	if !p.mode.final {
+		m = e.evalPartial(p.ctx)
+	} else {
+		m = p.ctx.manifest(e)
+	}
+	isComplete := p.isComplete(m, false)
+	if optional || (!isComplete && (!p.mode.concrete)) {
+		resolve := p.mode.resolveReferences && !optional
+		if !p.mode.final && v.kind().hasReferences() && !resolve {
+			return p.expr(v)
+		}
+		if p.mode.concrete && !m.kind().isGround() {
+			p.addIncomplete(v)
+		}
 		// TODO: do something more principled than this hack.
 		// This likely requires disjunctions to keep track of original
 		// values (so using arcs instead of values).
-		p := &exporter{p.ctx, options{concrete: true, raw: true}, p.stack, p.top, p.imports, p.inDef}
+		opts := options{concrete: true, raw: true}
+		p := &exporter{p.ctx, opts, p.stack, p.top, p.imports, p.inDef, nil}
 		if isDisjunction(v) || isBottom(e) {
 			return p.expr(v)
 		}
-		return p.expr(e)
+		if v.kind()&structKind == 0 {
+			return p.expr(e)
+		}
+		if optional {
+			// Break cycles: final and resolveReferences really should not be
+			// used with optional.
+			p.mode.resolveReferences = false
+			p.mode.final = false
+			return p.expr(v)
+		}
 	}
 	return p.expr(e)
 }
@@ -332,14 +372,25 @@
 	// as well.
 	if doEval(p.mode) || p.mode.concrete {
 		e := v.evalPartial(p.ctx)
-		x := p.ctx.manifest(e)
+		x := e
+		if p.mode.final {
+			x = p.ctx.manifest(e)
+		}
 
 		if !p.isComplete(x, true) {
+			if p.mode.concrete && !x.kind().isGround() {
+				p.addIncomplete(v)
+			}
 			if isBottom(e) {
-				p = &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports, p.inDef}
+				if p.mode.concrete {
+					p.addIncomplete(v)
+				}
+				p = &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports, p.inDef, nil}
 				return p.expr(v)
 			}
-			if doEval(p.mode) {
+			switch {
+			case !p.mode.final && v.kind().hasReferences() && !p.mode.resolveReferences:
+			case doEval(p.mode):
 				v = e
 			}
 		} else {
diff --git a/cue/export_test.go b/cue/export_test.go
index 51f5202..2f8a27a 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -318,7 +318,7 @@
 			job: {
 				list: {
 					name:     "list"
-					replicas: 1 @protobuf(10)
+					replicas: >=0 | *1 @protobuf(10)
 					command:  "ls"
 				}
 				nginx: {
@@ -396,23 +396,14 @@
 				bar: baz: 3
 			}
 			def :: {
-				a: 1
-				sub: {
-					foo: 1
-					bar: baz: 3
-				}
+				a:   1
+				sub: reg
 			}
-			val: {
-				a: 1
-				sub: {
-					foo: 1
-					bar: baz: 3
-				}
-			}
+			val: def
 			def2 :: {
 				a: b: int
 			}
-			val2: a: b: int
+			val2: def2
 		}`),
 	}, {
 		raw:  true,
@@ -437,7 +428,7 @@
 				}
 			}][a]
 			a: int
-			c: 1
+			c: *1 | 2
 		}`),
 	}, {
 		raw: true,
@@ -485,7 +476,7 @@
 				FindInMap :: {
 					"Fn::FindInMap" :: [string | FindInMap]
 				}
-				a: []
+				a: [...string]
 			}`)}, {
 		raw:   true,
 		eval:  true,
@@ -501,14 +492,15 @@
 		out: unindent(`
 			{
 				And :: {
-					"Fn::And": []
+					"Fn::And": [...3 | And]
 				}
-				Ands: "Fn::And": [3 | And]
+				Ands: And & {
+					"Fn::And": [_]
+				}
 			}`),
 	}, {
-		raw:   true,
-		eval:  true,
-		noOpt: true,
+		raw:  true,
+		eval: true,
 		in: `{
 			Foo :: {
 				sgl: Bar
@@ -527,19 +519,13 @@
 		out: unindent(`
 		{
 			FOO = Foo
-			FOO658221 = Foo
 			Foo :: {
-				Foo: 2
-				sgl: string
-				ref: null | {
-					Foo:  2
-					sgl:  Bar
-					ref:  (null | FOO) & (null | FOO)
-					ext:  Bar | null
-					ref2: null | FOO.sgl
-				}
+				Foo:  2
+				sgl:  Bar
+				ref:  (null | FOO) & (null | FOO)
 				ext:  Bar | null
-				ref2: null | FOO658221.sgl
+				ref2: null | FOO.sgl
+				...
 			}
 			Bar :: string
 		}`),
@@ -553,7 +539,7 @@
 		out: unindent(`
 		{
 			A: [>=0]
-			B: [10] | [192]
+			B: A & ([10] | [192])
 		}`),
 	}, {
 		in: `{
@@ -694,6 +680,9 @@
 
 		B: {}
 		B: {a: int} | {b: int}
+
+		C :: [D]: int
+		D :: string
 		`,
 		out: unindent(`
 		{
@@ -706,6 +695,11 @@
 			} | {
 				b: int
 			})
+			C :: {
+				[D]: int
+				[D]: int
+			}
+			D :: string
 		}`),
 	}, {
 		in: `
@@ -725,6 +719,7 @@
 		// a closed struct unified with a struct with a template restrictions is
 		// exported as a conjunction of two structs.
 		eval: true,
+		opts: []Option{ResolveReferences(true)},
 		in: `
 		A :: { b: int }
 		a: A & { [string]: <10 }
@@ -744,6 +739,7 @@
 		}`),
 	}, {
 		eval: true,
+		opts: []Option{Final()},
 		in: `{
 			reg: { foo: 1, bar: { baz: 3 } }
 			def :: {
@@ -759,27 +755,17 @@
 				foo: 1
 				bar: baz: 3
 			}
-			def :: {
-				a: 1
-				sub: {
-					foo: 1
-					bar: {
-						baz: 3
-						...
-					}
-					...
-				}
-			}
-			val: close({
+			val: {
 				a: 1
 				sub: {
 					foo: 1
 					bar: baz: 3
 				}
-			})
+			}
 		}`),
 	}, {
 		eval: true,
+		opts: []Option{ResolveReferences(true)},
 		in: `
 			T :: {
 				[_]: int64
@@ -805,7 +791,7 @@
 		}`),
 	}, {
 		eval: true,
-		opts: []Option{Optional(false)},
+		opts: []Option{Optional(false), ResolveReferences(true)},
 		in: `
 		T :: {
 			[_]: int64
@@ -826,6 +812,7 @@
 		}`),
 	}, {
 		eval: true,
+		opts: []Option{ResolveReferences(true)},
 		in: `{
 				reg: { foo: 1, bar: { baz: 3 } }
 				def :: {
@@ -888,6 +875,7 @@
 		}`),
 	}, {
 		eval: true,
+		opts: []Option{ResolveReferences(true)},
 		in: `
 		A :: {
 			[=~"^[a-s]*$"]: int
@@ -937,10 +925,93 @@
 			x: [string]: int
 			a: [P=string]: {
 				b: x[P]
-				c: string
+				c: P
 				e: len(P)
 			}
 		}`),
+	}, {
+		eval: true,
+		in: `
+		list: [...string]
+		foo: 1 | 2 | *3
+		foo: int
+		`,
+		out: unindent(`
+		{
+			list: [...string]
+			foo: 1 | 2 | *3
+		}`),
+	}, {
+		eval: true,
+		opts: []Option{Final()},
+		in: `
+		list: [...string]
+		foo: 1 | 2 | *3
+		foo: int
+		`,
+		out: unindent(`
+		{
+			list: []
+			foo: 3
+		}`),
+	}, {
+		// Expand final values, not values that may still be incomplete.
+		eval: true,
+		in: `
+		import "math"
+		import "tool/exec"
+
+		A :: {
+			b: 3
+		}
+
+		a:   A
+		pi:  math.Pi
+		run: exec.Run
+		`,
+		out: unindent(`
+		import "tool/exec"
+
+		A :: {
+			b: 3
+		}
+		a:   A
+		pi:  3.14159265358979323846264338327950288419716939937510582097494459
+		run: exec.Run`),
+	}, {
+		// Change mode midway to break cycle.
+		eval: true,
+		opts: []Option{Final(), Optional(true)},
+		in: `
+		Foo: {
+			foo?: Foo
+			bar:  string
+			baz:  bar + "2"
+		}
+
+		foo: Foo & {
+			foo: {
+				bar: "barNested"
+			}
+			bar: "barParent"
+		}`,
+		out: unindent(`
+		{
+			Foo: {
+				foo?: Foo
+				bar:  string
+				baz:  bar + "2"
+			}
+			foo: {
+				foo: {
+					foo?: Foo
+					bar:  "barNested"
+					baz:  "barNested2"
+				}
+				bar: "barParent"
+				baz: "barParent2"
+			}
+		}`),
 	}}
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {
diff --git a/cue/types.go b/cue/types.go
index ed269a7..79024cc 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1636,15 +1636,16 @@
 }
 
 type options struct {
-	concrete        bool // enforce that values are concrete
-	raw             bool // show original values
-	hasHidden       bool
-	omitHidden      bool
-	omitDefinitions bool
-	omitOptional    bool
-	omitAttrs       bool
-	final           bool
-	disallowCycles  bool // implied by concrete
+	concrete          bool // enforce that values are concrete
+	raw               bool // show original values
+	hasHidden         bool
+	omitHidden        bool
+	omitDefinitions   bool
+	omitOptional      bool
+	omitAttrs         bool
+	resolveReferences bool
+	final             bool
+	disallowCycles    bool // implied by concrete
 }
 
 // An Option defines modes of evaluation.
@@ -1655,7 +1656,12 @@
 // Final indicates a value is final. It implicitly closes all structs and lists
 // in a value and selects defaults.
 func Final() Option {
-	return func(o *options) { o.final = true }
+	return func(o *options) {
+		o.final = true
+		o.omitDefinitions = true
+		o.omitOptional = true
+		o.omitHidden = true
+	}
 }
 
 // Concrete ensures that all values are concrete.
@@ -1666,6 +1672,7 @@
 	return func(p *options) {
 		if concrete {
 			p.concrete = true
+			p.final = true
 			if !p.hasHidden {
 				p.omitHidden = true
 				p.omitDefinitions = true
@@ -1680,6 +1687,12 @@
 	return func(p *options) { p.disallowCycles = disallow }
 }
 
+// ResolveReferences forces the evaluation of references when outputting.
+// This implies the input cannot have cycles.
+func ResolveReferences(resolve bool) Option {
+	return func(p *options) { p.resolveReferences = resolve }
+}
+
 // Raw tells Syntax to generate the value as is without any simplifications.
 func Raw() Option {
 	return func(p *options) { p.raw = true }
diff --git a/internal/internal.go b/internal/internal.go
index 16f1e44..17af682 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -26,6 +26,7 @@
 	"github.com/cockroachdb/apd/v2"
 
 	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/token"
 )
 
@@ -37,6 +38,10 @@
 // DebugStr prints a syntax node.
 var DebugStr func(x interface{}) string
 
+// ErrIncomplete can be used by builtins to signal the evaluation was
+// incomplete.
+var ErrIncomplete = errors.New("incomplete value")
+
 // EvalExpr evaluates an expression within an existing struct value.
 // Identifiers only resolve to values defined within the struct.
 //