cue/parser: remove support for space-separated labels

Issue #339

Change-Id: Id3bf16f8f344cd9cfbcb51ce5d3a4359b8effafe
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5681
Reviewed-by: roger peppe <rogpeppe@gmail.com>
diff --git a/cmd/cue/cmd/fix_test.go b/cmd/cue/cmd/fix_test.go
index e7b64ba..15093b3 100644
--- a/cmd/cue/cmd/fix_test.go
+++ b/cmd/cue/cmd/fix_test.go
@@ -73,18 +73,6 @@
 	"\(k)": v
 }
 `,
-	}, {
-		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/legacy.txt b/cmd/cue/cmd/testdata/script/legacy.txt
index 1b92cc3..13cd68f 100644
--- a/cmd/cue/cmd/testdata/script/legacy.txt
+++ b/cmd/cue/cmd/testdata/script/legacy.txt
@@ -8,15 +8,12 @@
 cmp foo.cue foo-new.cue
 
 -- expect-stderr --
-space-separated labels deprecated as of v0.0.13: try running `cue fmt` on the file to upgrade.:
-    ./foo.cue:1:3
 old-style comprehensions deprecated as of v0.0.11: try running `cue fmt` on the file to upgrade.:
-    ./foo.cue:2:8
+    ./foo.cue:1:8
 -- foo.cue --
-a b: 2
 "x": 3 for x in a
 -- foo-new.cue --
-a: b: 2
+
 for x in a {
 	"x": 3
-}
\ No newline at end of file
+}
diff --git a/cue/ast/astutil/apply_test.go b/cue/ast/astutil/apply_test.go
index f6e412a..a9dc94a 100644
--- a/cue/ast/astutil/apply_test.go
+++ b/cue/ast/astutil/apply_test.go
@@ -136,12 +136,12 @@
 		name: "templates",
 		in: `
 				foo: {
-					a: <b>: c: 3
+					a: [string]: c: 3
 				}
 				`,
 		out: `
 foo: {
-	a: <b>: {
+	a: [string]: {
 		c:   3
 		iam: new
 	}
diff --git a/cue/ast_test.go b/cue/ast_test.go
index dfd8359..3cc17bd 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -150,7 +150,7 @@
 		in: `
 		a: 5 | "a" | true
 		aa: 5 | *"a" | true
-		b c: {
+		b: c: {
 			cc: { ccc: 3 }
 		}
 		d: true
@@ -158,8 +158,8 @@
 		out: "<0>{a: (5 | \"a\" | true), aa: (5 | *\"a\" | true), b: <1>{c: <2>{cc: <3>{ccc: 3}}}, d: true}",
 	}, {
 		in: `
-		a a: { b: a } // referencing ancestor nodes is legal.
-		a b: a.a      // do lookup before merging of nodes
+		a: a: { b: a } // referencing ancestor nodes is legal.
+		a: b: a.a      // do lookup before merging of nodes
 		b: a.a        // different node as a.a.b, as first node counts
 		c: a          // same node as b, as first node counts
 		d: a["a"]
@@ -282,11 +282,11 @@
 	}, {
 		in: `
 		a: {
-			<name>: { n: name }
+			[name=_]: { n: name }
 			k: 1
 		}
 		b: {
-			<X>: { x: 0, y: 1 }
+			[X=_]: { x: 0, y: 1 }
 			v: {}
 		}
 		`,
@@ -347,7 +347,7 @@
 		out: "unexpected ')':\n    test:2:18\nattribute missing ')':\n    test:3:3\n<0>{}",
 	}, {
 		in: `
-		a d: {
+		a: d: {
 			base
 			info :: {
 				...
@@ -359,7 +359,7 @@
 			info :: {...}
 		}
 
-		a <Name>: { info :: {
+		a: [Name=string]: { info :: {
 			X: "foo"
 		}}
 		`,
diff --git a/cue/build_test.go b/cue/build_test.go
index 90b15b7..14f7479 100644
--- a/cue/build_test.go
+++ b/cue/build_test.go
@@ -183,7 +183,7 @@
 				import bar "pkg1"
 				import baz "example.com/foo/pkg2:pkg"
 
-				pkg1 Object: 3
+				pkg1: Object: 3
 				"Hello \(pkg1.Object)!"`),
 		}),
 		`imported and not used: "pkg1" as bar (and 1 more errors)`,
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 3480268..8d3428a 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -258,7 +258,7 @@
 		test("list", `list.Sort([], list.Ascending)`),
 		`[]`,
 	}, {
-		test("list", `list.Sort([2, 3, 1, 4], {x:_, y:_, less: (x<y)})`),
+		test("list", `list.Sort([2, 3, 1, 4], {x:_, y:_, less: x<y})`),
 		`[1,2,3,4]`,
 	}, {
 		test("list", `list.SortStable([{a:2,v:1}, {a:1,v:2}, {a:1,v:3}], {
@@ -404,7 +404,7 @@
 		testExpr(`len({})`),
 		`0`,
 	}, {
-		testExpr(`len({a: 1, b: 2, <foo>: int, _c: 3})`),
+		testExpr(`len({a: 1, b: 2, [foo=_]: int, _c: 3})`),
 		`2`,
 	}, {
 		testExpr(`len([1, 2, 3])`),
diff --git a/cue/export_test.go b/cue/export_test.go
index af303e0..6967acb 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -281,8 +281,8 @@
 				idx: a[str]
 				str: string
 			}
-			b a b: 4
-			a b: 3
+			b: a: b: 4
+			a: b: 3
 		}`,
 		// reference to a must be redirected to outer a through alias
 		out: unindent(`
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index 5ed1f18..0dbc658 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -10,7 +10,7 @@
 
 	c: b: a:       4
 	c?: bb?: aaa?: 5
-	c: b: <Name>: a: int
+	c: b: [Name=string]: a: int
 	alias = 3.14
 	"g\("en")"?: 4
 
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index 3ea84a8..8b19de6 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -8,9 +8,9 @@
     
     b: 3
 
-    c b a:  4
-    c? bb? aaa?: 5
-    c b <Name> a: int
+    c: b: a:  4
+    c?: bb?: aaa?: 5
+    c: b: [Name=string]: a: int
     alias = 3.14
     "g\("en")"?: 4
 
diff --git a/cue/instance_test.go b/cue/instance_test.go
index 0a7ca56..e11cbf6 100644
--- a/cue/instance_test.go
+++ b/cue/instance_test.go
@@ -54,12 +54,12 @@
 	}, {
 		desc: "templates",
 		instances: insts(`
-			obj <X>: { a: "A" }
-			obj alpha: { b: 2 }
+			obj: [string]: { a: "A" }
+			obj: alpha: { b: 2 }
 			`,
 			`
-			obj <X>: { a: "B" }
-			obj beta: { b: 3 }
+			obj: [string]: { a: "B" }
+			obj: beta: { b: 3 }
 			`,
 		),
 		out: `{obj:{alpha:{a:A,b:2},beta:{a:B,b:3}}}`,
@@ -71,13 +71,13 @@
 		desc: "shared struct",
 		instances: insts(`
 			_shared: { a: "A" }
-			obj <X>: _shared & {}
-			obj alpha: { b: 2 }
+			obj: [string]: _shared & {}
+			obj: alpha: { b: 2 }
 			`,
 			`
 			_shared: { a: "B" }
-			obj <X>: _shared & {}
-			obj beta: { b: 3 }
+			obj: [string]: _shared & {}
+			obj: beta: { b: 3 }
 			`,
 		),
 		out: `{obj:{alpha:{a:A,b:2},beta:{a:B,b:3}}}`,
@@ -85,13 +85,13 @@
 		desc: "top-level comprehensions",
 		instances: insts(`
 			t: { for k, x in s {"\(k)": 10} }
-			s <Name>: {}
-			s foo a: 1
+			s: [string]: {}
+			s: foo: a: 1
 			`,
 			`
 			t: { for k, x in s {"\(k)": 10 } }
-			s <Name>: {}
-			s bar b: 2
+			s: [string]: {}
+			s: bar: b: 2
 			`,
 		),
 		out: `{t:{foo:10,bar:10},s:{foo:{a:1},bar:{b:2}}}`,
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index f1adb42..85c1061 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -786,7 +786,6 @@
 	this := &ast.Field{Label: nil}
 	m := this
 
-	multipleLabels := false
 	allowComprehension := true
 
 	for i := 0; ; i++ {
@@ -816,18 +815,14 @@
 			return e
 		}
 
-		if tok != token.LSS && p.tok == token.OPTION {
+		if p.tok == token.OPTION {
 			m.Optional = p.pos
 			p.next()
 		}
 
 		if p.tok == token.COLON || p.tok == token.ISA {
-			if p.tok == token.ISA && multipleLabels {
-				p.errf(p.pos, "more than one label before '::' (only one allowed)")
-			}
 			break
 		}
-		multipleLabels = true
 
 		// TODO: consider disallowing comprehensions with more than one label.
 		// This can be a bit awkward in some cases, but it would naturally
@@ -836,12 +831,6 @@
 		// allowComprehension = false
 
 		switch p.tok {
-		case token.IDENT, token.STRING, token.LSS, token.INTERPOLATION, token.LBRACK:
-			p.assertV0(p.pos, 0, 12, "space-separated labels")
-			field := &ast.Field{}
-			m.Value = &ast.StructLit{Elts: []ast.Decl{field}}
-			m = field
-
 		case token.COMMA:
 			p.expectComma() // sync parser.
 			fallthrough
@@ -988,7 +977,7 @@
 	switch tok {
 	case token.IDENT, token.STRING, token.INTERPOLATION,
 		token.NULL, token.TRUE, token.FALSE:
-		expr = p.parseExpr(true)
+		expr = p.parseExpr()
 
 		switch x := expr.(type) {
 		case *ast.BasicLit:
@@ -1047,76 +1036,12 @@
 			p.errf(pos, "expected operand, found '%s'", ident.Name)
 			expr = &ast.BadExpr{From: pos, To: p.pos}
 			// Sync expression.
-			expr = p.parseBinaryExprTail(false, token.LowestPrec+1, expr)
+			expr = p.parseBinaryExprTail(token.LowestPrec+1, expr)
 			expr = c.closeExpr(p, expr)
 			break
 		}
 		label = ident
 		ok = true
-
-	case token.LSS:
-		p.openList()
-		defer p.closeList()
-
-		pos := p.pos
-		c := p.openComments()
-		p.next()
-		var ident *ast.Ident
-		var gtr token.Pos
-		switch {
-		case rhs:
-			// TODO: remove this code once the <X> notation is out.
-			if p.tok != token.IDENT {
-				// parse RHS and continue binary Expression
-				expr = p.parseUnaryExpr()
-				expr = &ast.UnaryExpr{OpPos: pos, Op: token.LSS, X: expr}
-				expr = c.closeExpr(p, expr)
-				return nil, p.parseBinaryExprTail(false, token.LowestPrec+1, expr), false
-			}
-
-			ident = p.parseIdent()
-			if p.tok != token.GTR {
-				expr = p.parsePrimaryExprTail(ident)
-				expr = &ast.UnaryExpr{OpPos: pos, Op: token.LSS, X: expr}
-				expr = c.closeExpr(p, expr)
-				return nil, p.parseBinaryExprTail(false, token.LowestPrec+1, expr), false
-			}
-			gtr = p.pos
-			p.next()
-
-			// NOTE: at this point, even if there still a syntactically valid
-			// expression, it will always yield bottom, as '>' does not take
-			// boolean arguments.
-			// This code does not allow for `<X>[string]:` and so on. So a
-			// new way to alias labels should be introduced at the time
-			// such constructs are introduced. For instance `(X,Y)=[string]:`.
-			if p.tok != token.COLON && p.tok != token.ISA {
-				expr = &ast.UnaryExpr{OpPos: pos, Op: token.LSS, X: ident}
-				expr = c.closeExpr(p, expr)
-				c := p.openComments()
-				prec := token.GTR.Precedence()
-				expr = c.closeExpr(p, &ast.BinaryExpr{
-					X:     expr,
-					OpPos: gtr,
-					Op:    token.GTR,
-					Y:     p.checkExpr(p.parseBinaryExpr(false, prec)),
-				})
-				return nil, expr, false
-			}
-
-		default:
-			ident = p.parseIdent()
-			gtr = p.pos
-			if p.tok != token.GTR {
-				p.expect(token.GTR)
-			}
-			p.next()
-		}
-
-		p.assertV0(p.pos, 0, 12, "template labels")
-		label = &ast.TemplateLabel{Langle: pos, Ident: ident, Rangle: gtr}
-		c.closeNode(p, label)
-		ok = true
 	}
 	return label, expr, ok
 }
@@ -1291,7 +1216,7 @@
 	c := p.openComments()
 	defer func() { c.closeNode(p, expr) }()
 
-	expr = p.parseBinaryExprTail(false, token.LowestPrec+1, p.parseUnaryExpr())
+	expr = p.parseBinaryExprTail(token.LowestPrec+1, p.parseUnaryExpr())
 	expr = p.parseAlias(expr)
 
 	// Enforce there is an explicit comma. We could also allow the
@@ -1455,26 +1380,19 @@
 }
 
 // If lhs is set and the result is an identifier, it is not resolved.
-func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr {
+func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
 	if p.trace {
 		defer un(trace(p, "BinaryExpr"))
 	}
 	p.openList()
 	defer p.closeList()
 
-	return p.parseBinaryExprTail(lhs, prec1, p.parseUnaryExpr())
+	return p.parseBinaryExprTail(prec1, p.parseUnaryExpr())
 }
 
-func (p *parser) parseBinaryExprTail(lhs bool, prec1 int, x ast.Expr) ast.Expr {
+func (p *parser) parseBinaryExprTail(prec1 int, x ast.Expr) ast.Expr {
 	for {
 		op, prec := p.tokPrec()
-		if lhs && op == token.LSS {
-			// Eagerly interpret this as a template label.
-			// TODO: remove once <X> is deprecated.
-			if _, ok := x.(ast.Label); ok {
-				return x
-			}
-		}
 		if prec < prec1 {
 			return x
 		}
@@ -1486,7 +1404,7 @@
 			OpPos: pos,
 			Op:    op,
 			// Treat nested expressions as RHS.
-			Y: p.checkExpr(p.parseBinaryExpr(false, prec+1))})
+			Y: p.checkExpr(p.parseBinaryExpr(prec + 1))})
 	}
 }
 
@@ -1531,7 +1449,7 @@
 }
 
 // Callers must check the result (using checkExpr), depending on context.
-func (p *parser) parseExpr(lhs bool) (expr ast.Expr) {
+func (p *parser) parseExpr() (expr ast.Expr) {
 	if p.trace {
 		defer un(trace(p, "Expression"))
 	}
@@ -1539,11 +1457,11 @@
 	c := p.openComments()
 	defer func() { c.closeExpr(p, expr) }()
 
-	return p.parseBinaryExpr(lhs, token.LowestPrec+1)
+	return p.parseBinaryExpr(token.LowestPrec + 1)
 }
 
 func (p *parser) parseRHS() ast.Expr {
-	x := p.checkExpr(p.parseExpr(false))
+	x := p.checkExpr(p.parseExpr())
 	return x
 }
 
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index 46f4a03..45bdbe0 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -153,14 +153,6 @@
 		`,
 		`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",
 		`// 	$_: 1,
 			a: {b: {c: d}}
@@ -273,17 +265,17 @@
 	}, {
 		"duplicates allowed",
 		`{
-			a b: 3
+			a: b: 3
 			a: { b: 3 }
 		}`,
 		"{a: {b: 3}, a: {b: 3}}",
 	}, {
 		"templates", // TODO: remove
 		`{
-			<foo>: { a: int }
+			[foo=_]: { a: int }
 			a:     { a: 1 }
 		}`,
-		"{<foo>: {a: int}, a: {a: 1}}",
+		"{[foo=_]: {a: int}, a: {a: 1}}",
 	}, {
 		"foo",
 		`[
diff --git a/cue/parser/short_test.go b/cue/parser/short_test.go
index bfdb14c..da4e58a 100644
--- a/cue/parser/short_test.go
+++ b/cue/parser/short_test.go
@@ -21,7 +21,7 @@
 var valids = []string{
 	"\n",
 	`{}`,
-	`{ <Name>: foo }`,
+	`{ [Name=_]: foo }`,
 	`{ a: 3 }`,
 }
 
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 95f8e29..591b3c5 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -531,7 +531,7 @@
 		desc: "pick first",
 		in: `
 		a: *5 | "a" | true
-		b c: *{
+		b: c: *{
 			a: 2
 		} | {
 			a : 3
@@ -565,12 +565,12 @@
 func TestResolve(t *testing.T) {
 	testCases := []testCase{{
 		desc: "convert _ to top",
-		in:   `a: { <_>: _ }`,
+		in:   `a: { [_]: _ }`,
 		out:  `<0>{a: <1>{...}}`,
 	}, {
 		in: `
 			a: b.c.d
-			b c: { d: 3 }
+			b: c: { d: 3 }
 			c: { c: d.d, }
 			d: { d: 2 }
 			`,
@@ -1180,7 +1180,7 @@
 
 			Bar :: {
 				field: int
-				<A>:   int
+				[A=_]:   int
 			}
 			bar: Bar
 			bar: { feild: 2 }
@@ -1205,13 +1205,13 @@
 		in: `
 			// Allow combining of structs within a definition
 			D1 :: {
-				env a: "A"
-				env b: "B"
+				env: a: "A"
+				env: b: "B"
 				def :: {a: "A"}
 				def :: {b: "B"}
 			}
 
-			d1: D1 & { env c: "C" }
+			d1: D1 & { env: c: "C" }
 
 			D2 :: {
 				a: int
@@ -1221,15 +1221,15 @@
 			}
 
 			D3 :: {
-				env a: "A"
+				env: a: "A"
 			}
 			D3 :: {
-				env b: "B"
+				env: b: "B"
 			}
 
 			D4 :: {
 				env: DC
-				env b: int
+				env: b: int
 			}
 
 			DC :: { a: int }
@@ -1245,13 +1245,13 @@
 	}, {
 		desc: "recursive closing starting at non-definition",
 		in: `
-			z a: {
+			z: a: {
 				B:: {
-					c d: 1
-					c f: 1
+					c: d: 1
+					c: f: 1
 				}
 			}
-			A: z & { a: { B :: { c e: 2 } } }
+			A: z & { a: { B :: { c: e: 2 } } }
 			`,
 		out: `<0>{z: <1>{a: <2>{B :: <3>C{c: <4>C{d: 1, f: 1}}}}, A: <5>{a: <6>{B :: <7>C{c: _|_(2:field "e" not allowed in closed struct)}}}}`,
 	}, {
@@ -1317,7 +1317,7 @@
 					{ a: 1 } |
 					{ b: 2 }
 				}
-				x c: 3
+				x: c: 3
 			}
 					`,
 		out: `<0>{` +
@@ -1337,9 +1337,9 @@
 		}
 
 		// adding a field to a nested struct that is closed.
-		e1 :: S & { a d: 4 }
+		e1 :: S & { a: d: 4 }
 		// literal struct not closed until after unification.
-		v1 :: S & { a c: 4 }
+		v1 :: S & { a: c: 4 }
 		`,
 		out: `<0>{` +
 			`E :: <1>C{a: <2>C{b: int}}, ` +
@@ -1418,8 +1418,8 @@
 			b: B
 		}
 		V: S & {
-			c e: int
-			b extra: int
+			c: e: int
+			b: extra: int
 		}
 		`,
 		out: `<0>{` +
@@ -1548,12 +1548,12 @@
 		desc: "references from template to concrete",
 		in: `
 			res: [t]
-			t <X>: {
+			t: [X=string]: {
 				a: c + b.str
-				b str: string
+				b: str: string
 				c: "X"
 			}
-			t x: { b str: "DDDD" }
+			t: x: { b: str: "DDDD" }
 			`,
 		out: `<0>{res: [<1>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], t: <7>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`,
 	}, {
@@ -1644,17 +1644,17 @@
 		desc: "field templates",
 		in: `
 			a: {
-				<name>: int
+				[name=_]: int
 				k: 1
 			}
 			b: {
-				<X>: { x: 0, y: *1 | int }
+				[X=_]: { x: 0, y: *1 | int }
 				v: {}
 				w: { x: 0 }
 			}
-			b: { <y>: {} }
+			b: { [y=_]: {} }
 			c: {
-				<Name>: { name: Name, y: 1 }
+				[Name=_]: { name: Name, y: 1 }
 				foo: {}
 				bar: _
 			}
@@ -1764,11 +1764,11 @@
 	}, {
 		desc: "struct comprehensions",
 		in: `
-			obj foo a: "bar"
-			obj <Name>: {
+			obj: foo: a: "bar"
+			obj: [Name=string]: {
 				a: *"dummy" | string
 				if true {
-					sub as: a
+					sub: as: a
 				}
 			}
 
@@ -1810,7 +1810,7 @@
 				x: y+"?"
 				y: x+"!"
 			}
-			a x: "hey"
+			a: x: "hey"
 		`,
 		out: `<0>{a: <1>{x: _|_(("hey!?" & "hey"):conflicting values "hey!?" and "hey"), y: "hey!"}}`,
 	}, {
@@ -2032,30 +2032,30 @@
 	}, {
 		desc: "resolve all disjunctions",
 		in: `
-			service <Name>: {
+			service: [Name=string]: {
 				name: string | *Name
 				port: int | *7080
 			}
-			service foo: _
-			service bar: { port: 8000 }
-			service baz: { name: "foobar" }
+			service: foo: _
+			service: bar: { port: 8000 }
+			service: baz: { name: "foobar" }
 			`,
 		out: `<0>{service: <1>{[]: <2>(Name: string)-><3>{name: (string | *<2>.Name), port: (int | *7080)}, foo: <4>{name: "foo", port: 7080}, bar: <5>{name: "bar", port: 8000}, baz: <6>{name: "foobar", port: 7080}}}`,
 	}, {
 		desc: "field templates",
 		in: `
 			a: {
-				<name>: int
+				[name=_]: int
 				k: 1
 			}
 			b: {
-				<X>: { x: 0, y: *1 | int }
+				[X=_]: { x: 0, y: *1 | int }
 				v: {}
 				w: { y: 0 }
 			}
-			b: { <y>: {} } // TODO: allow different name
+			b: { [y=_]: {} } // TODO: allow different name
 			c: {
-				<Name>: { name: Name, y: 1 }
+				[Name=_]: { name: Name, y: 1 }
 				foo: {}
 				bar: _
 			}
@@ -2112,9 +2112,9 @@
 	}, {
 		desc: "referencing field in field comprehension",
 		in: `
-		a: { b c: 4 }
+		a: { b: c: 4 }
 		a: {
-			b d: 5
+			b: d: 5
 			for k, v in b {
 				"\(k)": v
 			}
@@ -2124,9 +2124,9 @@
 	}, {
 		desc: "different labels for templates",
 		in: `
-		a <X>: { name: X }
-		a <Name>: { name: Name }
-		a foo: {}
+		a: [X=string]: { name: X }
+		a: [Name=string]: { name: Name }
+		a: foo: {}
 		`,
 		out: `<0>{a: <1>{[]: <2>(X: string)->(<3>{name: <2>.X} & <4>{name: <2>.X}), foo: <5>{name: "foo"}}}`,
 	}, {
@@ -2134,13 +2134,13 @@
 
 		desc: "nested templates in one field",
 		in: `
-			a <A> b <B>: {
+			a: [A=string]: b: [B=string]: {
 				name: A
 				kind: B
 			}
-			a "A" b "B": _
-			a "C" b "D": _
-			a "EE" b "FF": { c: "bar" }
+			a: "A": b: "B": _
+			a: "C": b: "D": _
+			a: "EE": b: "FF": { c: "bar" }
 		`,
 		out: `<0>{a: <1>{[]: <2>(A: string)-><3>{b: <4>{[]: <5>(B: string)-><6>{name: <2>.A, kind: <5>.B}, }}, ` +
 			`A: <7>{b: <8>{[]: <9>(B: string)-><10>{name: <11>.A, kind: <9>.B}, ` +
@@ -2153,12 +2153,13 @@
 		desc: "template unification within one struct",
 		in: `
 			a: {
-				<A>: { name: A }
-				<A>: { kind: A }
+				[A=string]: { name: A }
+				// TODO: allow duplicate alias here
+				[X=string]: { kind: X }
 			}
-			a "A": _
-			a "C": _
-			a "E": { c: "bar" }
+			a: "A": _
+			a: "C": _
+			a: "E": { c: "bar" }
 		`,
 		out: `<0>{a: <1>{[]: <2>(A: string)->(<3>{name: <2>.A} & <4>{kind: <2>.A}), ` +
 			`E: <5>{name: "E", kind: "E", c: "bar"}, ` +
@@ -2172,7 +2173,7 @@
 				{a: "C", b: "D" },
 				{a: "E", b: "F" },
 			] {
-				a "\(x.a)" b "\(x.b)": x
+				a: "\(x.a)": b: "\(x.b)": x
 			}
 
 			for x in [
@@ -2180,7 +2181,7 @@
 				{a: "C", b: "D" },
 				{a: "E", b: "F" },
 			] {
-				"\(x.a)" "\(x.b)": x
+				"\(x.a)": "\(x.b)": x
 			}
 			`,
 		out: `<0>{E: <1>{F: <2>{a: "E", b: "F"}}, ` +
@@ -2204,13 +2205,13 @@
 			num: 1
 			a: {
 				if num < 5 {
-					<A> <B>: {
+					[A=string]: [B=string]: {
 						name: A
 						kind: B
 					}
 				}
 			}
-			a b c d: "bar"
+			a: b: c: d: "bar"
 			`,
 		out: `<0>{num: 1, a: <1>{[]: <2>(A: string)-><3>{[]: <4>(B: string)-><5>{name: <2>.A, kind: <4>.B}, }, ` +
 			`b: <6>{[]: <7>(B: string)-><8>{name: <9>.A, kind: <7>.B}, ` +
@@ -2264,14 +2265,14 @@
 		in: `
 			result: [ v for _, v in service ]
 
-			service <Name>: {
+			service: [Name=string]: {
 				name: *Name | string
 				type: "service"
 				port: *7080 | int
 			}
-			service foo: {}
-			service bar: { port: 8000 }
-			service baz: { name: "foobar" }
+			service: foo: {}
+			service: bar: { port: 8000 }
+			service: baz: { name: "foobar" }
 			`,
 		out: `<0>{result: [` +
 			`<1>{name: "foo", type: "service", port: 7080},` +
@@ -2338,8 +2339,8 @@
 			res: [ y & { d: "b" } for x in a for y in x ]
 			res: [ a.b.c & { d: "b" } ]
 
-			a b <C>: { d: string, s: "a" + d }
-			a b c d: string
+			a: b: [C=string]: { d: string, s: "a" + d }
+			a: b: c: d: string
 		`,
 		// TODO(perf): unification should catch shared node.
 		out: `<0>{res: [<1>{d: "b", s: "ab"}], ` +
@@ -2351,21 +2352,21 @@
 
 			f1: { y: string, res: a.b.c & { d: y } }
 
-			a b c: { d: string, s: "a" + d }
-			a b <C>: { d: string, s: "a" + d }
-			a b c d: string
+			a: b: c: { d: string, s: "a" + d }
+			a: b: [C=string]: { d: string, s: "a" + d }
+			a: b: c: d: string
 		`,
 		out: `<0>{r1: <1>{y: "c", res: <2>{d: "c", s: "ac"}}, f1: <3>{y: string, res: <4>{d: string, s: (("a" + <5>.d) & ("a" + <5>.d))}}, a: <6>{b: <7>{[]: <8>(C: string)-><9>{d: string, s: ("a" + <9>.d)}, c: <10>{d: string, s: (("a" + <11>.d) & ("a" + <11>.d))}}}}`,
 	}, {
 		desc: "references from template to concrete",
 		in: `
 				res: [t]
-				t <X>: {
+				t: [X=string]: {
 					a: c + b.str
-					b str: string
+					b: str: string
 					c: "X"
 				}
-				t x: { b str: "DDDD" }
+				t: x: { b: str: "DDDD" }
 				`,
 		out: `<0>{res: [<1>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], ` +
 			`t: <7>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`,
@@ -2543,7 +2544,7 @@
 	}, {
 		desc: "retain references with interleaved embedding",
 		in: `
-		a d: {
+		a: d: {
 			base
 			info :: {...}
 			Y: info.X
@@ -2553,7 +2554,7 @@
 			info :: {...}
 		}
 
-		a <Name>: { info :: {
+		a: [Name=string]: { info :: {
 			X: "foo"
 		}}
 		`,
@@ -2580,14 +2581,14 @@
 		in: `
 		Workflow :: {
 			jobs: {
-				<jobID>: {
+				[jobID=string]: {
 				}
 			}
 			JobID :: or([ k for k, _ in jobs ])
 		}
 
 		foo: Workflow & {
-			jobs foo: {
+			jobs: foo: {
 			}
 		}
 		`,
diff --git a/cue/types_test.go b/cue/types_test.go
index 284f9c2..1c90aec 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -940,32 +940,32 @@
 		want  string
 	}{{
 		value: `
-		a <Name>: Name
+		a: [Name=string]: Name
 		`,
 		path: []string{"a", ""},
 		want: `"label"`,
 	}, {
 		value: `
-		<Name>: { a: Name }
+		[Name=string]: { a: Name }
 		`,
 		path: []string{"", "a"},
 		want: `"label"`,
 	}, {
 		value: `
-		<Name>: { a: Name }
+		[Name=string]: { a: Name }
 		`,
 		path: []string{""},
 		want: `{"a":"label"}`,
 	}, {
 		value: `
-		a <Foo> <Bar>: { b: Foo+Bar }
+		a: [Foo=string]: [Bar=string]: { b: Foo+Bar }
 		`,
 		path: []string{"a", "", ""},
 		want: `{"b":"labellabel"}`,
 	}, {
 		value: `
-		a <Foo> b <Bar>: { c: Foo+Bar }
-		a foo b <Bar>: { d: Bar }
+		a: [Foo=string]: b: [Bar=string]: { c: Foo+Bar }
+		a: foo: b: [Bar=string]: { d: Bar }
 		`,
 		path: []string{"a", "foo", "b", ""},
 		want: `{"c":"foolabel","d":"label"}`,
@@ -1222,8 +1222,8 @@
 	}{{
 		desc: "issue #51",
 		in: `
-		a <Name>: foo
-		a b: {}
+		a: [string]: foo
+		a: b: {}
 		`,
 		err: true,
 	}, {
@@ -1231,7 +1231,7 @@
 		in: `
 		a: 1
 		b: { c: 2, d: 3 }
-		c d e f: 5
+		c: d: e: f: 5
 		g?: int
 		`,
 		opts: []Option{Concrete(true)},
@@ -1345,7 +1345,7 @@
 
 func TestPath(t *testing.T) {
 	config := `
-	a b c: 5
+	a: b: c: 5
 	b: {
 		b1: 3
 		b2: 4
@@ -1779,10 +1779,10 @@
 	}
 
 	// foos are instances of Foo.
-	foos <foo>: Foo
+	foos: [string]: Foo
 
 	// My first little foo.
-	foos MyFoo: {
+	foos: MyFoo: {
 		// local field comment.
 		field1: 0
 
diff --git a/encoding/protobuf/testdata/gateway.proto.out.cue b/encoding/protobuf/testdata/gateway.proto.out.cue
index 25d80c5..b5e3056 100644
--- a/encoding/protobuf/testdata/gateway.proto.out.cue
+++ b/encoding/protobuf/testdata/gateway.proto.out.cue
@@ -215,7 +215,7 @@
 	selector?: {
 		[string]: string
 	} @protobuf(2,type=map<string,string>)
-	selector?: <name>: name
+	selector?: [name=_]: name
 }
 
 // `Server` describes the properties of the proxy on a given load balancer
diff --git a/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway.proto b/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway.proto
index c69f42a..3f8f96b 100644
--- a/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway.proto
+++ b/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway.proto
@@ -220,7 +220,7 @@
   // label search is restricted to the configuration namespace in which the
   // the resource is present. In other words, the Gateway resource must
   // reside in the same namespace as the gateway workload instance.
-  map<string, string> selector = 2 [ (cue.val) = "{<name>: name}"];
+  map<string, string> selector = 2 [ (cue.val) = "{[name=_]: name}"];
 }
 
 // `Server` describes the properties of the proxy on a given load balancer
diff --git a/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue
index 9a20844..e9136a6 100644
--- a/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/networking/v1alpha3/gateway_proto_gen.cue
@@ -215,7 +215,7 @@
 	selector?: {
 		[string]: string
 	} @protobuf(2,type=map<string,string>)
-	selector?: {<name>: name}
+	selector?: {[name=_]: name}
 }
 
 // `Server` describes the properties of the proxy on a given load balancer