cue: move to square brackets

Change-Id: If40a5059a1f14f29bc7b7e557d3a93efc5ab9808
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3822
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/fix.go b/cmd/cue/cmd/fix.go
index 320b7f9..92a859e 100644
--- a/cmd/cue/cmd/fix.go
+++ b/cmd/cue/cmd/fix.go
@@ -58,7 +58,6 @@
 		return true
 	}, nil)
 
-	// Rewrite strings fields that are referenced.
 	referred := map[ast.Node]string{}
 	ast.Walk(f, func(n ast.Node) bool {
 		if i, ok := n.(*ast.Ident); ok {
@@ -71,6 +70,27 @@
 		return true
 	}, nil)
 
+	// Rewrite TemplateLabel to ListLit.
+	// Note: there is a chance that the name will clash with the
+	// scope in which it is defined. We drop the alias if it is not
+	// used to mitigate this issue.
+	f = astutil.Apply(f, func(c astutil.Cursor) bool {
+		n := c.Node()
+		switch x := n.(type) {
+		case *ast.TemplateLabel:
+			var expr ast.Expr = ast.NewIdent("string")
+			if _, ok := referred[x]; ok {
+				expr = &ast.Alias{
+					Ident: x.Ident,
+					Expr:  ast.NewIdent("_"),
+				}
+			}
+			c.Replace(ast.NewList(expr))
+		}
+		return true
+	}, nil).(*ast.File)
+
+	// Rewrite strings fields that are referenced.
 	f = astutil.Apply(f, func(c astutil.Cursor) bool {
 		n := c.Node()
 		switch x := n.(type) {
diff --git a/cmd/cue/cmd/fix_test.go b/cmd/cue/cmd/fix_test.go
index 88ab4b3..4ddeca2 100644
--- a/cmd/cue/cmd/fix_test.go
+++ b/cmd/cue/cmd/fix_test.go
@@ -108,6 +108,18 @@
 // f
 a: 3 + 5
 `,
+	}, {
+		name: "templates",
+		in: `package foo
+
+a: <Name>: { name: Name }
+b: <X>:    { name: string }
+`,
+		out: `package foo
+
+a: [Name=_]: {name: Name}
+b: [string]: {name: string}
+`,
 		// 	}, {
 		// 		name: "slice",
 		// 		in: `package foo
diff --git a/cmd/cue/cmd/testdata/script/trim.txt b/cmd/cue/cmd/testdata/script/trim.txt
index df4bd4f..a20ddbf 100644
--- a/cmd/cue/cmd/testdata/script/trim.txt
+++ b/cmd/cue/cmd/testdata/script/trim.txt
@@ -3,7 +3,7 @@
 -- expect-stdout --
 package trim
 
-foo: <Name>: {
+foo: [string]: {
 	_value: string
 
 	a: 4
@@ -21,7 +21,7 @@
 	rList: [{a: "a"}]
 	rcList: [{a: "a", c: b}]
 
-	t: <Name>: {
+	t: [string]: {
 		x: >=0 & <=5
 	}
 }
@@ -37,7 +37,7 @@
 foo: baz: {}
 
 foo: multipath: {
-	t: <Name>: {
+	t: [string]: {
 		// Combined with the other template, we know the value must be 5 and
 		// thus the entry below can be eliminated.
 		x: >=5 & <=8
@@ -60,7 +60,7 @@
 -- trim/trim.cue --
 package trim
 
-foo: <Name>: {
+foo: [string]: {
 	_value: string
 
 	a: 4
@@ -78,7 +78,7 @@
 	rList: [{a: "a"}]
 	rcList: [{a: "a", c: b}]
 
-	t: <Name>: {
+	t: [string]: {
 		x: >=0 & <=5
 	}
 }
@@ -94,7 +94,7 @@
 foo: baz: {}
 
 foo: multipath: {
-	t: <Name>: {
+	t: [string]: {
 		// Combined with the other template, we know the value must be 5 and
 		// thus the entry below can be eliminated.
 		x: >=5 & <=8
diff --git a/cue/ast.go b/cue/ast.go
index 08d6072..582cc46 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -25,6 +25,7 @@
 	"cuelang.org/go/cue/literal"
 	"cuelang.org/go/cue/token"
 	"cuelang.org/go/internal"
+	"golang.org/x/xerrors"
 )
 
 // insertFile inserts the given file at the root of the instance.
@@ -351,7 +352,15 @@
 				break
 			}
 		}
-		switch x := n.Label.(type) {
+
+		lab := n.Label
+		if a, ok := lab.(*ast.Alias); ok {
+			if lab, ok = a.Expr.(ast.Label); !ok {
+				return v.errf(n, "alias expression is not a valid label")
+			}
+		}
+
+		switch x := lab.(type) {
 		case *ast.Interpolation:
 			v.sel = "?"
 			// Must be struct comprehension.
@@ -366,6 +375,33 @@
 			}
 			v.object.comprehensions = append(v.object.comprehensions, fc)
 
+		case *ast.ListLit:
+			if len(x.Elts) != 1 {
+				return v.errf(x, "optional label expression must have exactly one element; found %d", len(x.Elts))
+			}
+			var f label
+			expr := x.Elts[0]
+			a, ok := expr.(*ast.Alias)
+			if ok {
+				expr = a.Expr
+				f = v.label(v.ident(a.Ident), true)
+			} else {
+				f = v.label("_", true)
+			}
+			if i, ok := expr.(*ast.Ident); !ok || (i.Name != "string" && i.Name != "_") {
+				return v.errf(x, `only 'string' or '_' allowed in this position`)
+			}
+			v.sel = "*"
+
+			sig := &params{}
+			sig.add(f, &basicType{newNode(lab), stringKind})
+			template := &lambdaExpr{newNode(n), sig, nil}
+
+			v.setScope(n, template)
+			template.value = v.walk(n.Value)
+
+			v.object.addTemplate(v.ctx(), token.NoPos, template)
+
 		case *ast.TemplateLabel:
 			if isDef {
 				v.errf(x, "map element type cannot be a definition")
@@ -374,7 +410,7 @@
 			f := v.label(v.ident(x.Ident), true)
 
 			sig := &params{}
-			sig.add(f, &basicType{newNode(n.Label), stringKind})
+			sig.add(f, &basicType{newNode(lab), stringKind})
 			template := &lambdaExpr{newNode(n), sig, nil}
 
 			v.setScope(n, template)
@@ -394,7 +430,7 @@
 			}
 			f, ok := v.nodeLabel(x)
 			if !ok {
-				return v.errf(n.Label, "invalid field name: %v", n.Label)
+				return v.errf(lab, "invalid field name: %v", lab)
 			}
 			if f != 0 {
 				val := v.walk(n.Value)
@@ -468,29 +504,72 @@
 			break
 		}
 
-		if a, ok := n.Node.(*ast.Alias); ok {
+		// Type of reference      Scope          Node
+		// Alias declaration      File/Struct    Alias
+		// Illegal Reference      File/Struct
+		// Fields
+		//    Label               File/Struct    ParenExpr, Ident, BasicLit
+		//    Value               File/Struct    Field
+		// Template               Field          Template
+		// Fields inside lambda
+		//    Label               Field          Expr
+		//    Value               Field          Field
+		// Pkg                    nil            ImportSpec
+
+		if x, ok := n.Node.(*ast.Alias); ok {
 			old := v.ctx().inDefinition
 			v.ctx().inDefinition = 0
-			ret = v.walk(a.Expr)
+			ret = v.walk(x.Expr)
 			v.ctx().inDefinition = old
 			break
 		}
 
 		f := v.label(name, true)
-		if n.Scope != nil {
-			n2 := v.mapScope(n.Scope)
-			if l, ok := n2.(*lambdaExpr); ok && len(l.params.arcs) == 1 {
-				f = 0
-			}
-			ret = &nodeRef{baseValue: newExpr(n), node: n2}
-			ret = &selectorExpr{newExpr(n), ret, f}
-		} else {
+		if n.Scope == nil {
 			// Package or direct ancestor node.
 			n2 := v.mapScope(n.Node)
 			ref := &nodeRef{baseValue: newExpr(n), node: n2, label: f}
 			ret = ref
+			break
 		}
 
+		n2 := v.mapScope(n.Scope)
+		ret = &nodeRef{baseValue: newExpr(n), node: n2}
+
+		l, lambda := n2.(*lambdaExpr)
+		if lambda && len(l.params.arcs) == 1 {
+			f = 0
+		}
+
+		if field, ok := n.Node.(*ast.Field); ok {
+			if lambda {
+				// inside bulk optional.
+				ret = v.errf(n, "referencing field (%q) within lambda not yet unsupported", name)
+				break
+			}
+			name, _, err := ast.LabelName(field.Label)
+			switch {
+			case xerrors.Is(err, ast.ErrIsExpression):
+				a := field.Label.(*ast.Alias)
+				ret = &indexExpr{newExpr(n), ret, v.walk(a.Expr)}
+
+			case err != nil:
+				ret = v.errf(n, "invalid label: %v", err)
+
+			case name != "":
+				f = v.label(name, true)
+				ret = &selectorExpr{newExpr(n), ret, f}
+
+			default:
+				// TODO: support dynamically computed label lookup.
+				// Should that also support lookup of definitions?
+				ret = v.errf(n, "unsupported field alias %q", name)
+			}
+			break
+		}
+
+		ret = &selectorExpr{newExpr(n), ret, f}
+
 	case *ast.BottomLit:
 		// TODO: record inline comment.
 		ret = &bottom{baseValue: newExpr(n), code: codeUser, format: "from source"}
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 96bc0c6..b631456 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -87,10 +87,11 @@
 // An Expr is implemented by all expression nodes.
 type Expr interface {
 	Node
+	declNode() // An expression can be used as a declaration.
 	exprNode()
 }
 
-type expr struct{}
+type expr struct{ decl }
 
 func (expr) exprNode() {}
 
@@ -324,6 +325,8 @@
 
 	comments
 	decl
+	expr
+	label
 }
 
 func (a *Alias) Pos() token.Pos  { return a.Ident.Pos() }
@@ -389,6 +392,8 @@
 }
 
 // A TemplateLabel represents a field template declaration in a struct.
+//
+// Deprecated: use square bracket notation through ListLit.
 type TemplateLabel struct {
 	Langle token.Pos
 	Ident  *Ident
@@ -449,6 +454,7 @@
 
 	comments
 	expr
+	label
 }
 
 // NewList creates a list of Expressions.
diff --git a/cue/ast/astutil/resolve.go b/cue/ast/astutil/resolve.go
index b09d629..b6f25cb 100644
--- a/cue/ast/astutil/resolve.go
+++ b/cue/ast/astutil/resolve.go
@@ -66,8 +66,30 @@
 	for _, d := range decls {
 		switch x := d.(type) {
 		case *ast.Field:
+			label := x.Label
+
+			if a, ok := x.Label.(*ast.Alias); ok {
+				if name, _ := internal.LabelName(a.Ident); name != "" {
+					s.insert(name, x)
+				}
+				label, _ = a.Expr.(ast.Label)
+			}
+
+			switch y := label.(type) {
+			// TODO: support *ast.ParenExpr?
+			case *ast.ListLit:
+				// In this case, it really should be scoped like a template.
+				if len(y.Elts) != 1 {
+					break
+				}
+				if a, ok := y.Elts[0].(*ast.Alias); ok {
+					s.insert(a.Ident.Name, x)
+				}
+			}
+
+			// default:
 			// TODO: switch to ast's implementation
-			name, isIdent := internal.LabelName(x.Label)
+			name, isIdent := internal.LabelName(label)
 			if isIdent {
 				s.insert(name, x.Value)
 			}
@@ -82,19 +104,40 @@
 	return s
 }
 
+func (s *scope) isAlias(n ast.Node) bool {
+	if _, ok := s.node.(*ast.Field); ok {
+		return true
+	}
+	switch n.(type) {
+	case *ast.Alias:
+		return true
+
+	case *ast.Field:
+		return true
+	}
+	return false
+}
+
 func (s *scope) insert(name string, n ast.Node) {
 	if name == "" {
 		return
 	}
-	if _, existing := s.lookup(name); existing != nil {
-		_, isAlias1 := n.(*ast.Alias)
-		_, isAlias2 := existing.(*ast.Alias)
+	// TODO: record both positions.
+	if outer, _, existing := s.lookup(name); existing != nil {
+		isAlias1 := s.isAlias(n)
+		isAlias2 := outer.isAlias(existing)
 		if isAlias1 != isAlias2 {
-			s.errFn(n.Pos(), "cannot have alias and non-alias with the same name")
+			s.errFn(n.Pos(), "cannot have both alias and field with name %q in same scope", name)
 			return
 		} else if isAlias1 || isAlias2 {
-			s.errFn(n.Pos(), "cannot have two aliases with the same name in the same scope")
-			return
+			if outer == s {
+				s.errFn(n.Pos(), "alias %q redeclared in same scope", name)
+				return
+			}
+			// TODO: Should we disallow shadowing of aliases?
+			// This was the case, but it complicates the transition to
+			// square brackets. The spec says allow it.
+			// s.errFn(n.Pos(), "alias %q already declared in enclosing scope", name)
 		}
 	}
 	s.index[name] = n
@@ -114,7 +157,7 @@
 	return nil, false
 }
 
-func (s *scope) lookup(name string) (obj, node ast.Node) {
+func (s *scope) lookup(name string) (p *scope, obj, node ast.Node) {
 	// TODO(#152): consider returning nil for obj if it is a reference to root.
 	// last := s
 	for s != nil {
@@ -122,12 +165,12 @@
 			// if last.node == n {
 			// 	return nil, n
 			// }
-			return s.node, n
+			return s, s.node, n
 		}
 		// s, last = s.outer, s
 		s = s.outer
 	}
-	return nil, nil
+	return nil, nil, nil
 }
 
 func (s *scope) After(n ast.Node) {}
@@ -151,9 +194,45 @@
 		s = scopeClauses(s, x.Clauses)
 
 	case *ast.Field:
-		switch label := x.Label.(type) {
+		var n ast.Node = x.Label
+		alias, ok := x.Label.(*ast.Alias)
+		if ok {
+			n = alias.Expr
+		}
+
+		switch label := n.(type) {
 		case *ast.Interpolation:
 			walk(s, label)
+
+		case *ast.ListLit:
+			if len(label.Elts) != 1 {
+				break
+			}
+			s := newScope(s.file, s, x, nil)
+			if alias != nil {
+				if name, _ := internal.LabelName(alias.Ident); name != "" {
+					s.insert(name, x)
+				}
+			}
+
+			a, ok := label.Elts[0].(*ast.Alias)
+			if ok {
+				// Simulate template label for now, for binding.
+
+				// Add to current scope, instead of the value's, and allow
+				// references to bind to these illegally.
+				// We need this kind of administration anyway to detect
+				// illegal name clashes, and it allows giving better error
+				// messages. This puts the burdon on clients of this library
+				// to detect illegal usage, though.
+				name, err := ast.ParseIdent(a.Ident)
+				if err == nil {
+					s.insert(name, a.Expr)
+				}
+			}
+			walk(s, x.Value)
+			return nil
+
 		case *ast.TemplateLabel:
 			s := newScope(s.file, s, x, nil)
 			name, err := ast.ParseIdent(label.Ident)
@@ -163,7 +242,6 @@
 			walk(s, x.Value)
 			return nil
 		}
-		// Disallow referring to the current LHS name (this applies recursively)
 		if x.Value != nil {
 			walk(s, x.Value)
 		}
@@ -191,9 +269,10 @@
 	case *ast.Ident:
 		name, ok, _ := ast.LabelName(x)
 		if !ok {
+			// TODO: generate error
 			break
 		}
-		if obj, node := s.lookup(name); node != nil {
+		if _, obj, node := s.lookup(name); node != nil {
 			switch {
 			case x.Node == nil:
 				x.Node = node
diff --git a/cue/ast/ident.go b/cue/ast/ident.go
index 58e7811..a626ba2 100644
--- a/cue/ast/ident.go
+++ b/cue/ast/ident.go
@@ -102,16 +102,26 @@
 //
 //     Label   Result
 //     foo     "foo"  true   nil
-//     `a-b`   "a-b"  true   nil
-//     true    true   true   nil
+//     true    "true" true   nil
 //     "foo"   "foo"  false  nil
-//     `a-b    ""     false  invalid identifier
+//     "x-y"   "x-y"  false  nil
 //     "foo    ""     false  invalid string
 //     "\(x)"  ""     false  errors.Is(err, ErrIsExpression)
-//     <A>     "A"    false  errors.Is(err, ErrIsExpression)
+//     X=foo   "foo"  true   nil
 //
 func LabelName(l Label) (name string, isIdent bool, err error) {
+	// XXX: alias unwrap once only.
 	switch n := l.(type) {
+	case *Alias:
+		if label, ok := n.Expr.(Label); ok {
+			return LabelName(label)
+		}
+
+	case *ListLit:
+		// An expression, but not one can evaluated.
+		return "", false, errors.Newf(l.Pos(),
+			"cannot reference fields with square brackets labels outside the field value")
+
 	case *Ident:
 		str, err := ParseIdent(n)
 		if err != nil {
@@ -137,7 +147,7 @@
 	}
 	// This includes interpolation and template labels.
 	return "", false, errors.Wrapf(ErrIsExpression, l.Pos(),
-		"label is interpolation or template")
+		"label is an expression")
 }
 
 // ErrIsExpression reports whether a label is an expression.
diff --git a/cue/ast_test.go b/cue/ast_test.go
index 09e9358..7e893c7 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -191,9 +191,9 @@
 		e2: "a"
 		e2 = "a"
 		`,
-		out: "cannot have two aliases with the same name in the same scope:\n" +
+		out: `alias "e1" redeclared in same scope:` + "\n" +
 			"    test:3:3\n" +
-			"cannot have alias and non-alias with the same name:\n" +
+			`cannot have both alias and field with name "e2" in same scope:` + "\n" +
 			"    test:6:3\n" +
 			"<0>{}",
 	}, {
@@ -204,6 +204,54 @@
 		}
 		`,
 		out: `<0>{b: <1>{c: <0>.b}}`,
+		// }, {
+		// 	// TODO: Support this:
+		// 	// optional fields
+		// 	in: `
+		// 		X=[string]: { chain: X | null }
+		// 		`,
+		// 	out: `
+		// 		`,
+	}, {
+		// optional fields
+		in: `
+			[ID=string]: { name: ID }
+			A="foo=bar": 3
+			a: A
+			B=bb: 4
+			b1: B
+			b1: bb
+			C="\(a)": 5
+			c: C
+			`,
+		out: `<0>{<>: <1>(ID: string)-><2>{name: <1>.ID}, foo=bar: 3, a: <0>.foo=bar, bb: 4, b1: (<0>.bb & <0>.bb), c: <0>[""+<0>.a+""]""+<0>.a+"": 5}`,
+	}, {
+		// illegal alias usage
+		in: `
+			[X=string]: { chain: X | null }
+			a: X
+			Y=[string]: 3
+			a: X
+			`,
+		out: `a: invalid label: cannot reference fields with square brackets labels outside the field value:
+    test:3:7
+a: invalid label: cannot reference fields with square brackets labels outside the field value:
+    test:5:7
+<0>{}`,
+	}, {
+		// detect duplicate aliases, even if illegal
+		in: `
+		[X=string]: int
+		X=[string]: int
+		Y=foo: int
+		Y=3
+		Z=[string]: { Z=3, a: int } // allowed
+		`,
+		out: `alias "X" redeclared in same scope:
+    test:3:3
+alias "Y" redeclared in same scope:
+    test:5:3
+<0>{}`,
 	}, {
 		in: `
 		a: {
@@ -211,11 +259,11 @@
 			k: 1
 		}
 		b: {
-			<x>: { x: 0, y: 1 }
+			<X>: { x: 0, y: 1 }
 			v: {}
 		}
 		`,
-		out: `<0>{a: <1>{<>: <2>(name: string)-><3>{n: <2>.name}, k: 1}, b: <4>{<>: <5>(x: string)-><6>{x: 0, y: 1}, v: <7>{}}}`,
+		out: `<0>{a: <1>{<>: <2>(name: string)-><3>{n: <2>.name}, k: 1}, b: <4>{<>: <5>(X: string)-><6>{x: 0, y: 1}, v: <7>{}}}`,
 	}, {
 		in: `
 		a: {
diff --git a/cue/format/format_test.go b/cue/format/format_test.go
index 0bd6104..ced3ed1 100644
--- a/cue/format/format_test.go
+++ b/cue/format/format_test.go
@@ -460,16 +460,13 @@
 // TextX is a skeleton test that can be filled in for debugging one-off cases.
 // Do not remove.
 func TestX(t *testing.T) {
+	t.Skip()
 	const src = `
-	{ e: k <-
-	for a, v in s}
-	a: b
-
 `
 	b, err := format([]byte(src), 0)
 	if err != nil {
 		t.Error(err)
 	}
 	_ = b
-	// t.Error("\n", string(b))
+	t.Error("\n", string(b))
 }
diff --git a/cue/format/node.go b/cue/format/node.go
index 7369b6d..e2572d6 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -421,6 +421,9 @@
 		f.label(n.Ident, false)
 		f.print(unindent, n.Rangle, token.GTR)
 
+	case *ast.ListLit:
+		f.expr(n)
+
 	case *ast.Interpolation:
 		f.expr(n)
 
@@ -464,6 +467,12 @@
 	case *ast.BottomLit:
 		f.print(x.Bottom, token.BOTTOM)
 
+	case *ast.Alias:
+		// Aliases in expression positions are printed in short form.
+		f.label(x.Ident, false)
+		f.print(x.Equal, token.BIND)
+		f.expr(x.Expr)
+
 	case *ast.Ident:
 		f.print(x.NamePos, x)
 
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 7d2ef07..296b19e 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -788,6 +788,16 @@
 			if expr == nil {
 				expr = p.parseRHS()
 			}
+			if a, ok := expr.(*ast.Alias); ok {
+				if i > 0 {
+					p.errorExpected(p.pos, "label or ':'")
+					return &ast.BadDecl{From: pos, To: p.pos}
+				}
+				if p.atComma("struct literal", token.RBRACE) {
+					p.next()
+				}
+				return a
+			}
 			e := &ast.EmbedDecl{Expr: expr}
 			if p.atComma("struct literal", token.RBRACE) {
 				p.next()
@@ -795,20 +805,6 @@
 			return e
 		}
 
-		if i == 0 && tok == token.IDENT {
-			ident := expr.(*ast.Ident)
-			switch p.tok {
-			case token.BIND:
-				pos := p.pos
-				p.expect(token.BIND)
-				ref := p.parseRHS()
-				if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF
-					p.next()
-				}
-				return &ast.Alias{Ident: ident, Equal: pos, Expr: ref}
-			}
-		}
-
 		if tok != token.LSS && p.tok == token.OPTION {
 			m.Optional = p.pos
 			p.next()
@@ -841,6 +837,9 @@
 
 		case token.RBRACE:
 			if i == 0 {
+				if a, ok := expr.(*ast.Alias); ok {
+					return a
+				}
 				switch tok {
 				case token.IDENT, token.LBRACK, token.STRING, token.INTERPOLATION,
 					token.NULL, token.TRUE, token.FALSE:
@@ -863,6 +862,10 @@
 	p.next() // : or ::
 
 	for {
+		if l, ok := m.Label.(*ast.ListLit); ok && len(l.Elts) != 1 {
+			p.errf(l.Pos(), "square bracket must have exactly one element")
+		}
+
 		tok := p.tok
 		label, expr, ok := p.parseLabel(true)
 		if !ok || (p.tok != token.COLON && p.tok != token.ISA && p.tok != token.OPTION) {
@@ -972,15 +975,33 @@
 			}
 
 		case *ast.Ident:
-			label, ok = x, true
 			if strings.HasPrefix(x.Name, "__") {
 				p.errf(x.NamePos, "identifiers starting with '__' are reserved")
 			}
 
+			expr = p.parseAlias(x)
+			if a, ok := expr.(*ast.Alias); ok {
+				if _, ok = a.Expr.(ast.Label); !ok {
+					break
+				}
+				label = a
+			} else {
+				label = x
+			}
+			ok = true
+
 		case ast.Label:
 			label, ok = x, true
 		}
 
+	case token.LBRACK:
+		expr = p.parseRHS()
+		switch x := expr.(type) {
+		case *ast.ListLit:
+			// Note: caller must verify this list is suitable as a label.
+			label, ok = x, true
+		}
+
 	case token.IF, token.FOR, token.IN, token.LET:
 		// Keywords representing clauses.
 		label = &ast.Ident{
@@ -1049,6 +1070,7 @@
 			p.next()
 		}
 
+		p.assertV0(0, 12, "template labels")
 		label = &ast.TemplateLabel{Langle: pos, Ident: ident, Rangle: gtr}
 		c.closeNode(p, label)
 		ok = true
@@ -1227,6 +1249,7 @@
 	defer func() { c.closeNode(p, expr) }()
 
 	expr = p.parseBinaryExprTail(false, token.LowestPrec+1, p.parseUnaryExpr())
+	expr = p.parseAlias(expr)
 
 	// Enforce there is an explicit comma. We could also allow the
 	// omission of commas in lists, but this gives rise to some ambiguities
@@ -1247,6 +1270,25 @@
 	return expr, true
 }
 
+// parseAlias turns an expression into an alias.
+func (p *parser) parseAlias(lhs ast.Expr) (expr ast.Expr) {
+	if p.tok != token.BIND {
+		return lhs
+	}
+	pos := p.pos
+	p.next()
+	expr = p.parseRHS()
+	if expr == nil {
+		panic("empty return")
+	}
+	switch x := lhs.(type) {
+	case *ast.Ident:
+		return &ast.Alias{Ident: x, Equal: pos, Expr: expr}
+	}
+	p.errf(p.pos, "expected identifier for alias")
+	return expr
+}
+
 // checkExpr checks that x is an expression (and not a type).
 func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 	switch unparen(x).(type) {
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index 6e50229..4148d6e 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -146,11 +146,19 @@
 		`package k8s, import a "foo", import "bar/baz"`,
 	}, {
 		"collapsed fields",
-		`a: b:: c?: <Name>: d: 1
+		`a: b:: c?: [Name=_]: d: 1
 		"g\("en")"?: 4
 		 // job foo { bar: 1 } // TODO error after foo
-		 job "foo" <X>: { bar: 1 }
+		 job: "foo": [_]: { bar: 1 }
 		`,
+		`a: {b :: {c?: {[Name=_]: {d: 1}}}}, "g\("en")"?: 4, job: {"foo": {[_]: {bar: 1}}}`,
+	}, {
+		"collapsed fields", // TODO: remove
+		`a: b:: c?: <Name>: d: 1
+			"g\("en")"?: 4
+			 // job foo { bar: 1 } // TODO error after foo
+			 job "foo" <X>: { bar: 1 }
+			`,
 		`a: {b :: {c?: {<Name>: {d: 1}}}}, "g\("en")"?: 4, job: {"foo": {<X>: {bar: 1}}}`,
 	}, {
 		"identifiers",
@@ -161,9 +169,9 @@
 			// e: a."b" // TODO: is an error
 			e: a.b.c
 			"f": f,
-			<X>: X
+			[X=_]: X
 		`,
-		"a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, <X>: X",
+		"a: {b: {c: d}}, c: a, d: a.b, e: a.b.c, \"f\": f, [X=_]: X",
 	}, {
 		"expressions",
 		`	a: (2 + 3) * 5
@@ -264,7 +272,7 @@
 		}`,
 		"{a: {b: 3}, a: {b: 3}}",
 	}, {
-		"templates",
+		"templates", // TODO: remove
 		`{
 			<foo>: { a: int }
 			a:     { a: 1 }
@@ -425,6 +433,26 @@
 ]
 `,
 		`{<[0// foo] [d0// fooo] foo: 1>, bar: 2}, [<[l4// each element has a long] {"name": "value"}>, <[l4// optional next element] {"name": "next"}>]`,
+	}, {
+		desc: "field aliasing",
+		in: `
+		I="\(k)": v
+		S="foo-bar": w
+		L=foo: x
+		X=[0]: {
+			foo: X | null
+		}
+		[Y=string]: { name: Y }
+		X1=[X2=<"d"]: { name: X2 }
+		Y1=foo: Y2=bar: [Y1, Y2]
+		`,
+		out: `I="\(k)": v, ` +
+			`S="foo-bar": w, ` +
+			`L=foo: x, ` +
+			`X=[0]: {foo: X|null}, ` +
+			`[Y=string]: {name: Y}, ` +
+			`X1=[X2=<"d"]: {name: X2}, ` +
+			`Y1=foo: {Y2=bar: [Y1, Y2]}`,
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
@@ -599,7 +627,7 @@
 			if sel == nil {
 				t.Fatalf("found no *SelectorExpr: %#v %s", f.Decls[0], debugStr(f))
 			}
-			const wantSel = "&{fmt _ {<nil>} {}}"
+			const wantSel = "&{fmt _ {<nil>} {{}}}"
 			if fmt.Sprint(sel) != wantSel {
 				t.Fatalf("found selector %v, want %s", sel, wantSel)
 			}
diff --git a/cue/parser/print.go b/cue/parser/print.go
index f72a636..b851505 100644
--- a/cue/parser/print.go
+++ b/cue/parser/print.go
@@ -51,7 +51,7 @@
 
 	case *ast.Alias:
 		out := debugStr(v.Ident)
-		out += " = "
+		out += "="
 		out += debugStr(v.Expr)
 		return out
 
diff --git a/cue/parser/short_test.go b/cue/parser/short_test.go
index 6e5e428..cba14bd 100644
--- a/cue/parser/short_test.go
+++ b/cue/parser/short_test.go
@@ -38,6 +38,7 @@
 		`foo !/* ERROR "expected label or ':', found '!'" */`,
 		`{ <Name
 			/* ERROR "expected '>', found newline" */ >: foo }`,
+		`foo: [/* ERROR "square bracket must have exactly one element" */ string, int]: int`,
 		// TODO:
 		// `{ </* ERROR "expected identifier, found newline" */
 		// 	Name>: foo }`,
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 7f1d4c9..4afa740 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -1640,7 +1640,7 @@
 				k: 1
 			}
 			b: {
-				<x>: { x: 0, y: *1 | int }
+				<X>: { x: 0, y: *1 | int }
 				v: {}
 				w: { x: 0 }
 			}
@@ -1651,7 +1651,7 @@
 				bar: _
 			}
 			`,
-		out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: (*1 | int)}, w: <8>{x: 0, y: (*1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
+		out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: (*1 | int)}, w: <8>{x: 0, y: (*1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
 	}, {
 		desc: "range unification",
 		in: `
@@ -2041,7 +2041,7 @@
 				k: 1
 			}
 			b: {
-				<x>: { x: 0, y: *1 | int }
+				<X>: { x: 0, y: *1 | int }
 				v: {}
 				w: { y: 0 }
 			}
@@ -2052,7 +2052,7 @@
 				bar: _
 			}
 			`,
-		out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
+		out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
 	}, {
 		desc: "field comprehension",
 		in: `
@@ -2604,6 +2604,25 @@
 		}
 		`,
 		out: `<0>{<1>{listOfCloseds: [_|_(2:field "b" not allowed in closed struct)]}, Foo: <2>{listOfCloseds: []}, Closed :: <3>C{a: 0}, Junk: <4>{b: 2}}`,
+	}, {
+		in: `
+		p: [ID=string]: { name: ID }
+		A="foo=bar": "str"
+		a: A
+		B=bb: 4
+		b1: B
+		b1: bb
+		C="\(a)": 5
+		c: C
+		`,
+		out: `<0>{` +
+			`p: <1>{<>: <2>(ID: string)-><3>{name: <2>.ID}, }, ` +
+			`foo=bar: "str", ` +
+			`a: "str", ` +
+			`bb: 4, ` +
+			`b1: 4, ` +
+			`c: 5, ` +
+			`str: 5}`,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
diff --git a/doc/tutorial/basics/0_intro/55_fold.txt b/doc/tutorial/basics/0_intro/55_fold.txt
index fc950e1..cf65ad0 100644
--- a/doc/tutorial/basics/0_intro/55_fold.txt
+++ b/doc/tutorial/basics/0_intro/55_fold.txt
@@ -27,7 +27,7 @@
 outer: middle2: inner: 7
 
 // collection-constraint pair
-outer: <Any>: inner: int
+outer: [string]: inner: int
 
 -- expect-stdout-cue --
 {
diff --git a/doc/tutorial/basics/2_types/90_templates.txt b/doc/tutorial/basics/2_types/90_templates.txt
index b08f49b..dac0e95 100644
--- a/doc/tutorial/basics/2_types/90_templates.txt
+++ b/doc/tutorial/basics/2_types/90_templates.txt
@@ -18,7 +18,7 @@
 -- templates.cue --
 // The following struct is unified with all elements in job.
 // The name of each element is bound to Name and visible in the struct.
-job: <Name>: {
+job: [Name=_]: {
     name:     Name
     replicas: uint | *1
     command:  string
diff --git a/doc/tutorial/kubernetes/quick/pkg/k8s.io/api/core/v1/types_go_gen.cue b/doc/tutorial/kubernetes/quick/pkg/k8s.io/api/core/v1/types_go_gen.cue
index ce7e297..f9e8799 100644
--- a/doc/tutorial/kubernetes/quick/pkg/k8s.io/api/core/v1/types_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/pkg/k8s.io/api/core/v1/types_go_gen.cue
@@ -5259,6 +5259,15 @@
 	// slightly more or slightly less than the specified limit.
 	// +optional
 	limitBytes?: null | int64 @go(LimitBytes,*int64) @protobuf(8,varint,opt)
+
+	// insecureSkipTLSVerifyBackend indicates that the apiserver should not confirm the validity of the
+	// serving certificate of the backend it is connecting to.  This will make the HTTPS connection between the apiserver
+	// and the backend insecure. This means the apiserver cannot verify the log data it is receiving came from the real
+	// kubelet.  If the kubelet is configured to verify the apiserver's TLS credentials, it does not mean the
+	// connection to the real kubelet is vulnerable to a man in the middle attack (e.g. an attacker could not intercept
+	// the actual log data coming from the real kubelet).
+	// +optional
+	insecureSkipTLSVerifyBackend?: bool @go(InsecureSkipTLSVerifyBackend) @protobuf(9,varint,opt)
 }
 
 // PodAttachOptions is the query options to a Pod's remote attach call.
diff --git a/doc/tutorial/kubernetes/quick/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr_go_gen.cue b/doc/tutorial/kubernetes/quick/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr_go_gen.cue
index e77c8a5..904500d 100644
--- a/doc/tutorial/kubernetes/quick/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr_go_gen.cue
@@ -16,7 +16,7 @@
 IntOrString :: _
 
 // Type represents the stored type of IntOrString.
-Type :: int // enumType
+Type :: int64 // enumType
 
 enumType ::
 	Int |
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/kube.cue
index d896430..6cea719 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/kube.cue
@@ -2,7 +2,7 @@
 
 Component :: "frontend"
 
-deployment: <X>: spec: template: {
+deployment: [string]: spec: template: {
 	metadata: annotations: {
 		"prometheus.io.scrape": "true"
 		"prometheus.io.port":   "\(spec.containers[0].ports[0].containerPort)"
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue
index 02fa1f9..5b77d2a 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue
@@ -2,7 +2,7 @@
 
 Component :: "kitchen"
 
-deployment: <Name>: spec: template: {
+deployment: [string]: spec: template: {
 	metadata: annotations: "prometheus.io.scrape": "true"
 	spec: containers: [{
 		ports: [{
@@ -19,7 +19,7 @@
 	}]
 }
 
-deployment: <ID>: spec: template: spec: {
+deployment: [ID=_]: spec: template: spec: {
 	hasDisks :: *true | bool
 
 	// field comprehension using just "if"
diff --git a/doc/tutorial/kubernetes/quick/services/kube.cue b/doc/tutorial/kubernetes/quick/services/kube.cue
index 40aa035..421cfa8 100644
--- a/doc/tutorial/kubernetes/quick/services/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kube.cue
@@ -1,6 +1,6 @@
 package kube
 
-service: <ID>: {
+service: [ID=_]: {
 	apiVersion: "v1"
 	kind:       "Service"
 	metadata: {
@@ -22,7 +22,7 @@
 	}
 }
 
-deployment: <ID>: {
+deployment: [ID=_]: {
 	apiVersion: "extensions/v1beta1"
 	kind:       "Deployment"
 	metadata: name: ID
@@ -43,26 +43,26 @@
 
 Component :: string
 
-daemonSet: <ID>: _spec & {
+daemonSet: [ID=_]: _spec & {
 	apiVersion: "extensions/v1beta1"
 	kind:       "DaemonSet"
 	Name ::     ID
 }
 
-statefulSet: <ID>: _spec & {
+statefulSet: [ID=_]: _spec & {
 	apiVersion: "apps/v1beta1"
 	kind:       "StatefulSet"
 	Name ::     ID
 }
 
-deployment: <ID>: _spec & {
+deployment: [ID=_]: _spec & {
 	apiVersion: "extensions/v1beta1"
 	kind:       "Deployment"
 	Name ::     ID
 	spec: replicas: *1 | int
 }
 
-configMap: <ID>: {
+configMap: [ID=_]: {
 	metadata: name: ID
 	metadata: labels: component: Component
 }