Skip to content

Commit 8823e2a

Browse files
committed
internal/core: support field value aliases
Support aliases of the form: a: X=b This implementation also allows for general alias values, but these have not yet been implemented as alias declarations are still being deprecated. Issue #620 Issue #380 Change-Id: Ide4c888bea187042898e8123480a1cfeb909914c Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9543 Reviewed-by: CUE cueckoo <[email protected]> Reviewed-by: Paul Jolly <[email protected]>
1 parent 67c6b6f commit 8823e2a

File tree

14 files changed

+627
-142
lines changed

14 files changed

+627
-142
lines changed

cue/ast/astutil/resolve.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ type ErrFunc func(pos token.Pos, msg string, args ...interface{})
5454
// Let Clause File/Struct LetClause
5555
// Alias declaration File/Struct Alias (deprecated)
5656
// Illegal Reference File/Struct
57+
// Value
58+
// X in a: X=y Field Alias
5759
// Fields
5860
// X in X: y File/Struct Expr (y)
5961
// X in X=x: y File/Struct Field
62+
// X in X=(x): y File/Struct Field
6063
// X in X="\(x)": y File/Struct Field
6164
// X in [X=x]: y Field Expr (x)
6265
// X in X=[x]: y Field Field
@@ -143,7 +146,12 @@ func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope
143146
// default:
144147
name, isIdent, _ := ast.LabelName(label)
145148
if isIdent {
146-
s.insert(name, x.Value, x)
149+
v := x.Value
150+
// Avoid interpreting value aliases at this point.
151+
if a, ok := v.(*ast.Alias); ok {
152+
v = a.Expr
153+
}
154+
s.insert(name, v, x)
147155
}
148156
case *ast.LetClause:
149157
name, isIdent, _ := ast.LabelName(x.Ident)
@@ -335,9 +343,16 @@ func (s *scope) Before(n ast.Node) (w visitor) {
335343
}
336344
}
337345

338-
if x.Value != nil {
346+
if n := x.Value; n != nil {
347+
if alias, ok := x.Value.(*ast.Alias); ok {
348+
// TODO: this should move into Before once decl attributes
349+
// have been fully deprecated and embed attributes are introduced.
350+
s = newScope(s.file, s, x, nil)
351+
s.insert(alias.Ident.Name, alias, x)
352+
n = alias.Expr
353+
}
339354
s.inField = true
340-
walk(s, x.Value)
355+
walk(s, n)
341356
s.inField = false
342357
}
343358

cue/parser/parser.go

Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -846,73 +846,62 @@ func (p *parser) parseField() (decl ast.Decl) {
846846
this := &ast.Field{Label: nil}
847847
m := this
848848

849-
for i := 0; ; i++ {
850-
tok := p.tok
851-
852-
label, expr, decl, ok := p.parseLabel(false)
853-
if decl != nil {
854-
return decl
855-
}
856-
m.Label = label
849+
tok := p.tok
857850

858-
if !ok {
859-
if expr == nil {
860-
expr = p.parseRHS()
861-
}
862-
if a, ok := expr.(*ast.Alias); ok {
863-
if i > 0 {
864-
p.errorExpected(p.pos, "label or ':'")
865-
return &ast.BadDecl{From: pos, To: p.pos}
866-
}
867-
p.consumeDeclComma()
868-
return a
869-
}
870-
e := &ast.EmbedDecl{Expr: expr}
871-
p.consumeDeclComma()
872-
return e
873-
}
851+
label, expr, decl, ok := p.parseLabel(false)
852+
if decl != nil {
853+
return decl
854+
}
855+
m.Label = label
874856

875-
if p.tok == token.OPTION {
876-
m.Optional = p.pos
877-
p.next()
857+
if !ok {
858+
if expr == nil {
859+
expr = p.parseRHS()
878860
}
879-
880-
if p.tok == token.COLON || p.tok == token.ISA {
881-
break
861+
if a, ok := expr.(*ast.Alias); ok {
862+
p.consumeDeclComma()
863+
return a
882864
}
865+
e := &ast.EmbedDecl{Expr: expr}
866+
p.consumeDeclComma()
867+
return e
868+
}
883869

884-
// TODO: consider disallowing comprehensions with more than one label.
885-
// This can be a bit awkward in some cases, but it would naturally
886-
// enforce the proper style that a comprehension be defined in the
887-
// smallest possible scope.
888-
// allowComprehension = false
870+
if p.tok == token.OPTION {
871+
m.Optional = p.pos
872+
p.next()
873+
}
889874

890-
switch p.tok {
891-
case token.COMMA:
892-
p.expectComma() // sync parser.
893-
fallthrough
875+
// TODO: consider disallowing comprehensions with more than one label.
876+
// This can be a bit awkward in some cases, but it would naturally
877+
// enforce the proper style that a comprehension be defined in the
878+
// smallest possible scope.
879+
// allowComprehension = false
894880

895-
case token.RBRACE, token.EOF:
896-
if i == 0 {
897-
if a, ok := expr.(*ast.Alias); ok {
898-
p.assertV0(p.pos, 1, 3, `old-style alias; use "let X = expr"`)
881+
switch p.tok {
882+
case token.COLON, token.ISA:
883+
case token.COMMA:
884+
p.expectComma() // sync parser.
885+
fallthrough
899886

900-
return a
901-
}
902-
switch tok {
903-
case token.IDENT, token.LBRACK, token.LPAREN,
904-
token.STRING, token.INTERPOLATION,
905-
token.NULL, token.TRUE, token.FALSE,
906-
token.FOR, token.IF, token.LET, token.IN:
907-
return &ast.EmbedDecl{Expr: expr}
908-
}
909-
}
910-
fallthrough
887+
case token.RBRACE, token.EOF:
888+
if a, ok := expr.(*ast.Alias); ok {
889+
p.assertV0(p.pos, 1, 3, `old-style alias; use "let X = expr"`)
911890

912-
default:
913-
p.errorExpected(p.pos, "label or ':'")
914-
return &ast.BadDecl{From: pos, To: p.pos}
891+
return a
915892
}
893+
switch tok {
894+
case token.IDENT, token.LBRACK, token.LPAREN,
895+
token.STRING, token.INTERPOLATION,
896+
token.NULL, token.TRUE, token.FALSE,
897+
token.FOR, token.IF, token.LET, token.IN:
898+
return &ast.EmbedDecl{Expr: expr}
899+
}
900+
fallthrough
901+
902+
default:
903+
p.errorExpected(p.pos, "label or ':'")
904+
return &ast.BadDecl{From: pos, To: p.pos}
916905
}
917906

918907
m.TokenPos = p.pos
@@ -936,9 +925,6 @@ func (p *parser) parseField() (decl ast.Decl) {
936925
if expr == nil {
937926
expr = p.parseRHS()
938927
}
939-
if a, ok := expr.(*ast.Alias); ok {
940-
p.errf(expr.Pos(), "alias %q not allowed as value", debugStr(a.Ident))
941-
}
942928
m.Value = expr
943929
break
944930
}

cue/parser/parser_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ func TestParse(t *testing.T) {
329329
a: { a: 1 }
330330
}`,
331331
"{[foo=_]: {a: int}, a: {a: 1}}",
332+
}, {
333+
"value alias",
334+
`
335+
{
336+
a: X=foo
337+
b: Y={foo}
338+
c: d: e: X=5
339+
}
340+
`,
341+
`{a: X=foo, b: Y={foo}, c: {d: {e: X=5}}}`,
332342
}, {
333343
"dynamic labels",
334344
`{
@@ -567,7 +577,7 @@ bar: 2
567577
in: `
568578
a: int=>2
569579
`,
570-
out: "a: int=>2\nalias \"int\" not allowed as value",
580+
out: "a: int=>2",
571581
}, {
572582
desc: "struct comments",
573583
in: `

cue/testdata/references/value.txtar

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
-- in.cue --
2+
structShorthand: X={b: 3, c: X.b}
3+
4+
// Note that X and Y are subtly different, as they have different bindings:
5+
// one binds to the field, the other to the value. In this case, that does not
6+
// make a difference.
7+
fieldAndValue: X=foo: Y={ 3, #sum: X + Y }
8+
9+
valueCycle: b: X=3+X
10+
11+
-- out/eval --
12+
(struct){
13+
structShorthand: (struct){
14+
b: (int){ 3 }
15+
c: (int){ 3 }
16+
}
17+
fieldAndValue: (struct){
18+
foo: (int){
19+
3
20+
#sum: (int){ 6 }
21+
}
22+
}
23+
valueCycle: (struct){
24+
b: (_|_){
25+
// [cycle] cycle error:
26+
// ./in.cue:8:18
27+
}
28+
}
29+
}
30+
-- out/compile --
31+
--- in.cue
32+
{
33+
structShorthand: {
34+
b: 3
35+
c: 〈1〉.b
36+
}
37+
fieldAndValue: {
38+
foo: {
39+
3
40+
#sum: (〈1;foo〉 + 〈1〉)
41+
}
42+
}
43+
valueCycle: {
44+
b: (3 + 〈0〉)
45+
}
46+
}

internal/core/adt/adt.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ func (*Builtin) expr() {}
206206

207207
func (*NodeLink) expr() {}
208208
func (*FieldReference) expr() {}
209+
func (*ValueReference) expr() {}
209210
func (*LabelReference) expr() {}
210211
func (*DynamicReference) expr() {}
211212
func (*ImportReference) expr() {}
@@ -281,6 +282,8 @@ func (*NodeLink) declNode() {}
281282
func (*NodeLink) elemNode() {}
282283
func (*FieldReference) declNode() {}
283284
func (*FieldReference) elemNode() {}
285+
func (*ValueReference) declNode() {}
286+
func (*ValueReference) elemNode() {}
284287
func (*LabelReference) declNode() {}
285288
func (*LabelReference) elemNode() {}
286289
func (*DynamicReference) declNode() {}
@@ -338,6 +341,7 @@ func (*ListLit) node() {}
338341
func (*BoundExpr) node() {}
339342
func (*NodeLink) node() {}
340343
func (*FieldReference) node() {}
344+
func (*ValueReference) node() {}
341345
func (*LabelReference) node() {}
342346
func (*DynamicReference) node() {}
343347
func (*ImportReference) node() {}

internal/core/adt/expr.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,31 @@ func (x *FieldReference) resolve(c *OpContext, state VertexStatus) *Vertex {
714714
return c.lookup(n, pos, x.Label, state)
715715
}
716716

717+
// A ValueReference represents a lexical reference to a value.
718+
//
719+
// a: X=b
720+
//
721+
type ValueReference struct {
722+
Src *ast.Ident
723+
UpCount int32
724+
Label Feature // for informative purposes
725+
}
726+
727+
func (x *ValueReference) Source() ast.Node {
728+
if x.Src == nil {
729+
return nil
730+
}
731+
return x.Src
732+
}
733+
734+
func (x *ValueReference) resolve(c *OpContext, state VertexStatus) *Vertex {
735+
if x.UpCount == 0 {
736+
return c.vertex
737+
}
738+
n := c.relNode(x.UpCount - 1)
739+
return n
740+
}
741+
717742
// A LabelReference refers to the string or integer value of a label.
718743
//
719744
// [X=Pattern]: b: X

0 commit comments

Comments
 (0)