cue: fix exporting of label aliases

These could sometimes be dropped when  exported.
This also changes the default expression (if none is
specified) from '_' to 'string'.

Change-Id: I5713ffec945aab1c289f551bf814e0123b85647b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3961
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast.go b/cue/ast.go
index 2794987..ec77515 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -538,6 +538,9 @@
 		n2 := v.mapScope(n.Scope)
 		ret = &nodeRef{baseValue: newExpr(n), node: n2}
 
+		// Allow different names to refer to the same field in unification. We
+		// do this by anonymizing the the reference. This then has to be
+		// resolved again when refering to lambdas.
 		l, lambda := n2.(*lambdaExpr)
 		if lambda && len(l.params.arcs) == 1 {
 			f = 0
diff --git a/cue/export.go b/cue/export.go
index f9aee37..c3d2c84 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -205,12 +205,12 @@
 	if v != nil {
 		expr = p.expr(v)
 	} else {
-		expr = ast.NewIdent("_")
+		expr = ast.NewIdent("string")
 	}
 	switch n.Name {
 	case "", "_":
 	default:
-		expr = &ast.Alias{Ident: n, Expr: ast.NewIdent("_")}
+		expr = &ast.Alias{Ident: n, Expr: ast.NewIdent("string")}
 	}
 	return ast.NewList(expr)
 }
@@ -312,6 +312,16 @@
 	return x.closeStatus.shouldClose()
 }
 
+func (p *exporter) badf(msg string, args ...interface{}) ast.Expr {
+	msg = fmt.Sprintf(msg, args...)
+	bad := &ast.BadExpr{}
+	bad.AddComment(&ast.CommentGroup{
+		Doc:  true,
+		List: []*ast.Comment{{Text: "// " + msg}},
+	})
+	return bad
+}
+
 func (p *exporter) expr(v value) ast.Expr {
 	// TODO: use the raw expression for convert incomplete errors downstream
 	// as well.
@@ -365,17 +375,24 @@
 		if n != nil {
 			return ast.NewSel(n, p.ctx.labelStr(x.feature))
 		}
-		ident := p.identifier(x.feature)
+		f := x.feature
+		ident := p.identifier(f)
 		node, ok := x.x.(*nodeRef)
 		if !ok {
-			// TODO: should not happen: report error
+			return p.badf("selector without node")
+		}
+		if l, ok := node.node.(*lambdaExpr); ok && len(l.arcs) == 1 {
+			f = l.params.arcs[0].feature
+			// TODO: ensure it is shadowed.
+			ident = p.identifier(f)
 			return ident
 		}
-		// TODO: nodes may have changed. Use different algorithm.
+
+		// TODO: nodes may have been shadowed. Use different algorithm.
 		conflict := false
 		for i := len(p.stack) - 1; i >= 0; i-- {
 			e := &p.stack[i]
-			if e.from != x.feature {
+			if e.from != f {
 				continue
 			}
 			if e.key != node.node {
@@ -385,18 +402,17 @@
 			if conflict {
 				ident = e.to
 				if e.to == nil {
-					name := p.unique(p.ctx.labelStr(x.feature))
+					name := p.unique(p.ctx.labelStr(f))
 					e.syn.Elts = append(e.syn.Elts, &ast.Alias{
 						Ident: p.ident(name),
-						Expr:  p.identifier(x.feature),
+						Expr:  p.identifier(f),
 					})
 					ident = p.ident(name)
 					e.to = ident
 				}
 			}
-			return ident
+			break
 		}
-		// TODO: should not happen: report error
 		return ident
 
 	case *indexExpr:
diff --git a/cue/export_test.go b/cue/export_test.go
index eefbfa2..688f6f2 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -365,8 +365,8 @@
 				emb
 			}
 			e :: {
-				[_]: <100
-				b:   int
+				[string]: <100
+				b:        int
 				f
 			}
 		}`),
@@ -429,7 +429,7 @@
 		out: unindent(`
 		{
 			b: [{
-				[X=_]: int
+				[X=string]: int
 				if a > 4 {
 					f: 4
 				}
@@ -686,7 +686,7 @@
 		out: unindent(`
 		{
 			A: {
-				[_]: B
+				[string]: B
 			} @protobuf(1,"test")
 			B: {
 			} & ({
@@ -715,7 +715,7 @@
 		eval: true,
 		in: `
 		A :: { b: int }
-		a: A & { [_]: <10 }
+		a: A & { [string]: <10 }
 		B :: a
 		`,
 		out: unindent(`
@@ -780,15 +780,15 @@
 		out: unindent(`
 		{
 			T :: {
-				[_]: int64
+				[string]: int64
 			}
 			X :: {
-				[_]: int64
-				x:   int64
+				[string]: int64
+				x:        int64
 			}
 			x: {
-				[_]: int64
-				x:   int64
+				[string]: int64
+				x:        int64
 			}
 		}`),
 	}, {
@@ -910,6 +910,25 @@
 				})
 			}
 		}`),
+	}, {
+		eval: true,
+		in: `
+		x: [string]: int
+		a: [P=string]: {
+			b: x[P]
+			c: P
+			e: len(P)
+		}
+		`,
+		out: unindent(`
+		{
+			x: [string]: int
+			a: [P=string]: {
+				b: x[P]
+				c: string
+				e: len(P)
+			}
+		}`),
 	}}
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {