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 := ¶ms{}
+ 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 := ¶ms{}
- 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
}