cue: support export package clause and docs

Change-Id: I5db5b8e8bc6180061334c230770a20c4cadb6949
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5092
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast_test.go b/cue/ast_test.go
index bc8acd1..bb2d77e 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -610,7 +610,7 @@
 
 			ctx := r.index().newContext()
 
-			n, _ := export(ctx, inst.rootStruct, options{
+			n, _ := export(ctx, inst, inst.rootStruct, options{
 				raw: true,
 			})
 			got := internal.DebugStr(n)
diff --git a/cue/export.go b/cue/export.go
index 80d2255..4767b83 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -34,7 +34,7 @@
 	return !m.raw
 }
 
-func export(ctx *context, v value, m options) (n ast.Node, imports []string) {
+func export(ctx *context, inst *Instance, v value, m options) (n ast.Node, imports []string) {
 	e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}, false, nil}
 	top, ok := v.evalPartial(ctx).(*structLit)
 	if ok {
@@ -49,32 +49,49 @@
 	}
 
 	value := e.expr(v)
-	if len(e.imports) == 0 {
+	if len(e.imports) == 0 && inst == nil {
 		// TODO: unwrap structs?
 		return value, nil
 	}
+
+	file := &ast.File{}
+	if inst != nil {
+		if inst.Name != "" {
+			p := &ast.Package{Name: ast.NewIdent(inst.Name)}
+			file.Decls = append(file.Decls, p)
+			if m.docs {
+				for _, d := range inst.Doc() {
+					p.AddComment(d)
+					break
+				}
+			}
+		}
+	}
+
 	imports = make([]string, 0, len(e.imports))
 	for k := range e.imports {
 		imports = append(imports, k)
 	}
 	sort.Strings(imports)
 
-	importDecl := &ast.ImportDecl{}
-	file := &ast.File{Decls: []ast.Decl{importDecl}}
+	if len(imports) > 0 {
+		importDecl := &ast.ImportDecl{}
+		file.Decls = append(file.Decls, importDecl)
 
-	for _, k := range imports {
-		info := e.imports[k]
-		ident := (*ast.Ident)(nil)
-		if info.name != "" {
-			ident = ast.NewIdent(info.name)
+		for _, k := range imports {
+			info := e.imports[k]
+			ident := (*ast.Ident)(nil)
+			if info.name != "" {
+				ident = ast.NewIdent(info.name)
+			}
+			if info.alias != "" {
+				file.Decls = append(file.Decls, &ast.Alias{
+					Ident: ast.NewIdent(info.alias),
+					Expr:  ast.NewIdent(info.short),
+				})
+			}
+			importDecl.Specs = append(importDecl.Specs, ast.NewImport(ident, k))
 		}
-		if info.alias != "" {
-			file.Decls = append(file.Decls, &ast.Alias{
-				Ident: ast.NewIdent(info.alias),
-				Expr:  ast.NewIdent(info.short),
-			})
-		}
-		importDecl.Specs = append(importDecl.Specs, ast.NewImport(ident, k))
 	}
 
 	if obj, ok := value.(*ast.StructLit); ok {
@@ -859,6 +876,12 @@
 				f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
 			}
 		}
+		if p.mode.docs {
+			for _, d := range a.docs.appendDocs(nil) {
+				ast.AddComment(f, d)
+				break
+			}
+		}
 		obj.Elts = append(obj.Elts, f)
 	}
 
diff --git a/cue/export_test.go b/cue/export_test.go
index 2f8a27a..62f1b85 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -567,7 +567,7 @@
 			v := newValueRoot(ctx, n)
 
 			opts := options{raw: !tc.eval, omitOptional: tc.noOpt}
-			node, _ := export(ctx, v.eval(ctx), opts)
+			node, _ := export(ctx, nil, v.eval(ctx), opts)
 			b, err := format.Node(node, format.Simplify())
 			if err != nil {
 				log.Fatal(err)
@@ -585,22 +585,36 @@
 		in, out string
 		opts    []Option
 	}{{
+		eval: true,
+		opts: []Option{Docs(true)},
 		in: `
+		// Hello foo
+		package foo
+
 		import "strings"
 
 		a: strings.ContainsAny("c")
 		`,
 		out: unindent(`
+		// Hello foo
+		package foo
+
 		import "strings"
 
 		a: strings.ContainsAny("c")`),
 	}, {
+		eval: true,
 		in: `
+		// Drop this comment
+		package foo
+
 		import "time"
 
 		a: time.Time
 		`,
 		out: unindent(`
+		package foo
+
 		import "time"
 
 		a: time.Time`),
@@ -669,10 +683,8 @@
 			b: a + 100
 		`,
 		out: unindent(`
-		{
-			a: b - 100
-			b: a + 100
-		}`),
+		a: b - 100
+		b: a + 100`),
 	}, {
 		in: `A: {
 			[_]: B
@@ -685,22 +697,20 @@
 		D :: string
 		`,
 		out: unindent(`
-		{
-			A: {
-				[string]: B
-			} @protobuf(1,"test")
-			B: {
-			} & ({
-				a: int
-			} | {
-				b: int
-			})
-			C :: {
-				[D]: int
-				[D]: int
-			}
-			D :: string
-		}`),
+		A: {
+			[string]: B
+		} @protobuf(1,"test")
+		B: {
+		} & ({
+			a: int
+		} | {
+			b: int
+		})
+		C :: {
+			[D]: int
+			[D]: int
+		}
+		D :: string`),
 	}, {
 		in: `
 		import "time"
@@ -726,16 +736,14 @@
 		B :: a
 		`,
 		out: unindent(`
-		{
-			A :: {
-				b: int
-			}
-			a: close({
-				b: <10
-			})
-			B :: {
-				b: <10
-			}
+		A :: {
+			b: int
+		}
+		a: close({
+			b: <10
+		})
+		B :: {
+			b: <10
 		}`),
 	}, {
 		eval: true,
@@ -776,18 +784,16 @@
 			x: X
 			`,
 		out: unindent(`
-		{
-			T :: {
-				[string]: int64
-			}
-			x: {
-				[string]: int64
-				x:        int64
-			}
-			X :: {
-				[string]: int64
-				x:        int64
-			}
+		T :: {
+			[string]: int64
+		}
+		x: {
+			[string]: int64
+			x:        int64
+		}
+		X :: {
+			[string]: int64
+			x:        int64
 		}`),
 	}, {
 		eval: true,
@@ -802,13 +808,11 @@
 		x: X
 		`,
 		out: unindent(`
-		{
-			T :: {
-			}
-			x: x: int64
-			X :: {
-				x: int64
-			}
+		T :: {
+		}
+		x: x: int64
+		X :: {
+			x: int64
 		}`),
 	}, {
 		eval: true,
@@ -868,11 +872,9 @@
 				c?: 3
 				c: 3`,
 		out: unindent(`
-		{
-			a?: 1
-			b?: 2
-			c:  3
-		}`),
+		a?: 1
+		b?: 2
+		c:  3`),
 	}, {
 		eval: true,
 		opts: []Option{ResolveReferences(true)},
@@ -888,30 +890,51 @@
 		`,
 		// TODO: the outer close of C could be optimized away.
 		out: unindent(`
-		{
-			A :: {
+		A :: {
+			[=~"^[a-s]*$"]: int
+		}
+		B :: {
+			[=~"^[m-z]+"]: int
+		}
+		C: close({
+			close({
 				[=~"^[a-s]*$"]: int
-			}
-			B :: {
+			}) & close({
 				[=~"^[m-z]+"]: int
-			}
-			C: close({
-				close({
-					[=~"^[a-s]*$"]: int
-				}) & close({
-					[=~"^[m-z]+"]: int
-				})
 			})
-			D :: {
-				close({
-					[=~"^[a-s]*$"]: int
-				}) & close({
-					[=~"^[m-z]+"]: int
-				})
-			}
+		})
+		D :: {
+			close({
+				[=~"^[a-s]*$"]: int
+			}) & close({
+				[=~"^[m-z]+"]: int
+			})
 		}`),
 	}, {
 		eval: true,
+		opts: []Option{Docs(true)},
+		in: `
+		// Definition
+		A :: {
+			// TODO: support
+			[string]: int
+		}
+		// Field
+		a: A
+
+		// Pick first comment only.
+		a: _
+		`,
+		out: unindent(`
+		// Definition
+		A :: {
+			[string]: int
+		}
+
+		// Field
+		a: A`),
+	}, {
+		eval: true,
 		in: `
 		x: [string]: int
 		a: [P=string]: {
@@ -921,13 +944,11 @@
 		}
 		`,
 		out: unindent(`
-		{
-			x: [string]: int
-			a: [P=string]: {
-				b: x[P]
-				c: P
-				e: len(P)
-			}
+		x: [string]: int
+		a: [P=string]: {
+			b: x[P]
+			c: P
+			e: len(P)
 		}`),
 	}, {
 		eval: true,
@@ -937,10 +958,8 @@
 		foo: int
 		`,
 		out: unindent(`
-		{
-			list: [...string]
-			foo: 1 | 2 | *3
-		}`),
+		list: [...string]
+		foo: 1 | 2 | *3`),
 	}, {
 		eval: true,
 		opts: []Option{Final()},
diff --git a/cue/load/loader_test.go b/cue/load/loader_test.go
index 6071db3..b0090d8 100644
--- a/cue/load/loader_test.go
+++ b/cue/load/loader_test.go
@@ -330,13 +330,13 @@
 			t.Error(inst.Err)
 			continue
 		}
-		b, err := format.Node(inst.Value().Syntax())
+		b, err := format.Node(inst.Value().Syntax(cue.Final()))
 		if err != nil {
 			t.Error(err)
 			continue
 		}
 		if got := string(bytes.Map(rmSpace, b)); got != want[i] {
-			t.Errorf("%s: got %s; want %s", inst.Dir, got, want)
+			t.Errorf("%s: got %s; want %s", inst.Dir, got, want[i])
 		}
 	}
 }
diff --git a/cue/marshal.go b/cue/marshal.go
index 885bbb1..e8cc7f3 100644
--- a/cue/marshal.go
+++ b/cue/marshal.go
@@ -137,7 +137,8 @@
 		if p, ok := done[i.ImportPath]; ok {
 			return p
 		}
-		n, imports := export(ctx, i.rootValue, options{raw: true})
+		// TODO: support exporting instance
+		n, imports := export(ctx, nil, i.rootValue, options{raw: true})
 
 		file, ok := n.(*ast.File)
 		if !ok {
diff --git a/cue/types.go b/cue/types.go
index 79024cc..a013eb0 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -839,11 +839,15 @@
 	}
 	ctx := v.ctx()
 	o := getOptions(opts)
+	var inst *Instance
+	if !o.final && !o.concrete {
+		inst = v.instance()
+	}
 	if o.raw {
-		n, _ := export(ctx, v.path.v, o)
+		n, _ := export(ctx, inst, v.path.v, o)
 		return n
 	}
-	n, _ := export(ctx, v.path.cache, o)
+	n, _ := export(ctx, inst, v.path.cache, o)
 	return n
 }
 
@@ -1488,6 +1492,13 @@
 	_, _ = io.WriteString(state, ctx.str(v.path.cache))
 }
 
+func (v Value) instance() *Instance {
+	if v.path == nil {
+		return nil
+	}
+	return v.ctx().getImportFromNode(v.path.v)
+}
+
 // Reference returns the instance and path referred to by this value such that
 // inst.Lookup(path) resolves to the same value, or no path if this value is not
 // a reference. If a reference contains index selection (foo[bar]), it will
@@ -1645,6 +1656,7 @@
 	omitAttrs         bool
 	resolveReferences bool
 	final             bool
+	docs              bool
 	disallowCycles    bool // implied by concrete
 }
 
@@ -1709,6 +1721,11 @@
 	}
 }
 
+// Docs indicates whether docs should be included.
+func Docs(include bool) Option {
+	return func(p *options) { p.docs = true }
+}
+
 // Definitions indicates whether definitions should be included.
 //
 // Definitions may still be included for certain functions if they are referred
diff --git a/cue/types_test.go b/cue/types_test.go
index 075f468..3d2ae62 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -30,6 +30,7 @@
 
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/internal"
 )
 
 func getInstance(t *testing.T, body ...string) *Instance {
@@ -697,7 +698,7 @@
 	x: int
 }
 X :: {
-	<_>: int64
+	[string]: int64
 } & V
 v: X
 `)
@@ -725,7 +726,8 @@
 			t.Errorf("got %v; want %v", got, tc.raw)
 		}
 
-		if got := fmt.Sprint(v.Eval().Syntax()); got != tc.eval {
+		got := fmt.Sprint(internal.DebugStr(v.Eval().Syntax()))
+		if got != tc.eval {
 			t.Errorf("got %v; want %v", got, tc.eval)
 		}
 
@@ -746,7 +748,8 @@
 			t.Errorf("got %v; want %v", got, tc.raw)
 		}
 
-		if got := fmt.Sprint(v.Eval().Syntax()); got != tc.eval {
+		got = fmt.Sprint(internal.DebugStr(v.Eval().Syntax()))
+		if got != tc.eval {
 			t.Errorf("got %v; want %v", got, tc.eval)
 		}
 	}
diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go
index 33cb400..56fd6cc 100644
--- a/encoding/jsonschema/decode.go
+++ b/encoding/jsonschema/decode.go
@@ -123,7 +123,7 @@
 }
 
 func (d *decoder) number(n cue.Value) ast.Expr {
-	return n.Syntax().(ast.Expr)
+	return n.Syntax(cue.Final()).(ast.Expr)
 }
 
 func (d *decoder) uint(n cue.Value) ast.Expr {
@@ -131,11 +131,11 @@
 	if err != nil {
 		d.errf(n, "invalid uint")
 	}
-	return n.Syntax().(ast.Expr)
+	return n.Syntax(cue.Final()).(ast.Expr)
 }
 
 func (d *decoder) bool(n cue.Value) ast.Expr {
-	return n.Syntax().(ast.Expr)
+	return n.Syntax(cue.Final()).(ast.Expr)
 }
 
 func (d *decoder) boolValue(n cue.Value) bool {
@@ -147,7 +147,7 @@
 }
 
 func (d *decoder) string(n cue.Value) ast.Expr {
-	return n.Syntax().(ast.Expr)
+	return n.Syntax(cue.Final()).(ast.Expr)
 }
 
 func (d *decoder) strValue(n cue.Value) (s string, ok bool) {
@@ -343,7 +343,7 @@
 		if !n.IsConcrete() {
 			s.errf(n, "invalid non-concerte value")
 		}
-		return n.Syntax().(ast.Expr)
+		return n.Syntax(cue.Final()).(ast.Expr)
 	}
 }
 
diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go
index 209dda6..9e2b07d 100644
--- a/encoding/openapi/types.go
+++ b/encoding/openapi/types.go
@@ -51,7 +51,7 @@
 	default:
 		return ""
 	}
-	b, err := format.Node(v.Syntax())
+	b, err := format.Node(v.Syntax(cue.Final()))
 	if err != nil {
 		return ""
 	}
diff --git a/tools/trim/trim.go b/tools/trim/trim.go
index ccbb197..7c632c8 100644
--- a/tools/trim/trim.go
+++ b/tools/trim/trim.go
@@ -278,7 +278,14 @@
 				// resolved. Attempt to complete the expression by
 				// evaluting it within the struct to which the template
 				// is applied.
-				expr := v.Syntax()
+				var expr ast.Expr
+				switch x := v.Syntax().(type) {
+				case ast.Expr:
+					expr = x
+				case *ast.File:
+					expr = &ast.StructLit{Elts: x.Decls}
+				}
+
 				// TODO: this only resolves references contained in scope.
 				v = internal.EvalExpr(scope, expr).(cue.Value)
 			}