diff --git a/cue/ast.go b/cue/ast.go index 27949871e..ec7751530 100644 --- a/cue/ast.go +++ b/cue/ast.go @@ -538,6 +538,9 @@ func (v *astVisitor) walk(astNode ast.Node) (ret value) { 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 f9aee376c..c3d2c844f 100644 --- a/cue/export.go +++ b/cue/export.go @@ -205,12 +205,12 @@ func (p *exporter) mkTemplate(v value, n *ast.Ident) ast.Label { 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 @@ func (p *exporter) isClosed(x *structLit) bool { 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 @@ func (p *exporter) expr(v value) ast.Expr { 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 @@ func (p *exporter) expr(v value) ast.Expr { 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 eefbfa2b9..688f6f26d 100644 --- a/cue/export_test.go +++ b/cue/export_test.go @@ -365,8 +365,8 @@ func TestExport(t *testing.T) { emb } e :: { - [_]: <100 - b: int + [string]: <100 + b: int f } }`), @@ -429,7 +429,7 @@ func TestExport(t *testing.T) { out: unindent(` { b: [{ - [X=_]: int + [X=string]: int if a > 4 { f: 4 } @@ -686,7 +686,7 @@ func TestExportFile(t *testing.T) { out: unindent(` { A: { - [_]: B + [string]: B } @protobuf(1,"test") B: { } & ({ @@ -715,7 +715,7 @@ func TestExportFile(t *testing.T) { eval: true, in: ` A :: { b: int } - a: A & { [_]: <10 } + a: A & { [string]: <10 } B :: a `, out: unindent(` @@ -780,15 +780,15 @@ func TestExportFile(t *testing.T) { 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 @@ func TestExportFile(t *testing.T) { }) } }`), + }, { + 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) {