cue: add Struct type

For most CUE types there is a way to express
the type as a Go type in builtin functions.
This is not possible for generic structs.

This CL adds the Struct type to the cue API.
Unfortunately this duplicates some of the API.
It could be considered removing some of the
old API accordingly.

Change-Id: I565fa5215f91786cc5a713713bef9f12d72cbafe
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2684
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index 89b1028..21167d5 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -347,6 +347,16 @@
 	return newValueRoot(c.ctx, c.args[i])
 }
 
+func (c *callCtxt) structVal(i int) *Struct {
+	v := newValueRoot(c.ctx, c.args[i])
+	s, err := v.Struct()
+	if err != nil {
+		c.invalidArgType(c.args[i], i, "struct", err)
+		return nil
+	}
+	return s
+}
+
 func (c *callCtxt) invalidArgType(arg value, i int, typ string, err error) {
 	if err != nil {
 		c.errf(c.src, err, "cannot use %s (type %s) as %s in argument %d to %s: %v",
diff --git a/cue/gen.go b/cue/gen.go
index 4e866a5..89394c0 100644
--- a/cue/gen.go
+++ b/cue/gen.go
@@ -222,6 +222,9 @@
 	}
 	g.defaultPkg = ""
 	g.name = f.Name.Name
+	if g.name == "structs" {
+		g.name = "struct"
+	}
 
 	for _, d := range f.Decls {
 		switch x := d.(type) {
@@ -396,6 +399,8 @@
 		return "bigRat"
 	case "internal.Decimal":
 		return "decimal"
+	case "cue.Struct":
+		return "structVal"
 	case "cue.Value":
 		return "value"
 	case "cue.List":
@@ -436,6 +441,8 @@
 	case "strList":
 		omitCheck = false
 		cueKind += "listKind"
+	case "structVal":
+		cueKind += "structKind"
 	case "value":
 		// Must use callCtxt.value method for these types and resolve manually.
 		cueKind += "topKind" // TODO: can be more precise
diff --git a/cue/types.go b/cue/types.go
index 2074fe1..deb08d1 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -107,8 +107,10 @@
 	}
 	if i == len {
 		// TODO: better message.
-		return newValueRoot(o.ctx, o.ctx.mkErr(o.n, codeNotExist,
-			"value %q not found", key))
+		ctx := o.ctx
+		x := ctx.mkErr(o.n, codeNotExist, "value %q not found", key)
+		v := x.evalPartial(ctx)
+		return Value{ctx.index, &valueData{o.path, 0, arc{cache: v, v: x}}}
 	}
 	// v, _ := o.n.lookup(o.ctx, f)
 	// v = o.ctx.manifest(v)
@@ -1026,7 +1028,7 @@
 
 // structVal returns an structVal or an error if v is not a struct.
 func (v Value) structValOpts(ctx *context, o options) (structValue, *bottom) {
-	v, _ = v.Default()
+	v, _ = v.Default() // TODO: remove?
 	if err := v.checkKind(ctx, structKind); err != nil {
 		return structValue{}, err
 	}
@@ -1070,6 +1072,63 @@
 	return structValue{ctx, v.path, obj}, nil
 }
 
+// Struct returns the underlying struct of a value or an error if the value
+// is not a struct.
+func (v Value) Struct(opts ...Option) (*Struct, error) {
+	ctx := v.ctx()
+	if err := v.checkKind(ctx, structKind); err != nil {
+		return nil, v.toErr(err)
+	}
+	obj := v.eval(ctx).(*structLit)
+
+	// TODO: This is expansion appropriate?
+	obj = obj.expandFields(ctx)
+
+	return &Struct{v, obj}, nil
+}
+
+// Struct represents a CUE struct value.
+type Struct struct {
+	v Value
+	s *structLit
+}
+
+// FieldInfo contains information about a struct field.
+type FieldInfo struct {
+	Name  string
+	Value Value
+
+	IsOptional bool
+	IsHidden   bool
+}
+
+// field reports information about the ith field, i < o.Len().
+func (s *Struct) field(i int) FieldInfo {
+	ctx := s.v.ctx()
+	a := s.s.arcs[i]
+	a.cache = s.s.at(ctx, i)
+
+	v := Value{ctx.index, &valueData{s.v.path, uint32(i), a}}
+	str := ctx.labelStr(a.feature)
+	return FieldInfo{str, v, a.optional, a.feature&hidden != 0}
+}
+
+func (s *Struct) FieldByName(name string) (FieldInfo, bool) {
+	f := s.v.ctx().strLabel(name)
+	for i, a := range s.s.arcs {
+		if a.feature == f {
+			return s.field(i), true
+		}
+	}
+	return FieldInfo{}, false
+}
+
+// Fields creates an iterator over the Struct's fields.
+func (s *Struct) Fields(opts ...Option) Iterator {
+	iter, _ := s.v.Fields(opts...)
+	return iter
+}
+
 // Fields creates an iterator over v's fields if v is a struct or an error
 // otherwise.
 func (v Value) Fields(opts ...Option) (Iterator, error) {