cue: closedness only applies to regular fields
Issue #40
Change-Id: I6f2cf753e84bc570dfe1fc91cda1eccb29f0582e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3042
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/binop.go b/cue/binop.go
index 99ad043..b9b6149 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -653,7 +653,7 @@
break
}
}
- if !unchecked && !found && !y.allows(a.feature) {
+ if !unchecked && !found && !y.allows(a.feature) && !a.definition {
if a.optional {
continue
}
@@ -691,7 +691,7 @@
continue outer
}
}
- if !unchecked && !found && !x.allows(a.feature) {
+ if !unchecked && !found && !x.allows(a.feature) && !a.definition {
if a.optional {
continue
}
diff --git a/cue/export.go b/cue/export.go
index 9ea6c8b..8cd1518 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -215,6 +215,11 @@
}
func (p *exporter) closeOrOpen(s *ast.StructLit, isClosed bool) ast.Expr {
+ // Note, there is no point in printing close if we are dropping optional
+ // fields, as by this the meaning of close will change anyway.
+ if p.mode.omitOptional || p.mode.concrete {
+ return s
+ }
if isClosed && !p.inDef {
return &ast.CallExpr{
Fun: ast.NewIdent("close"),
diff --git a/cue/export_test.go b/cue/export_test.go
index 760a135..a8c7bc3 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -27,6 +27,7 @@
testCases := []struct {
raw bool // skip evaluation the root, fully raw
eval bool // evaluate the full export
+ noOpt bool
in, out string
}{{
in: `"hello"`,
@@ -100,30 +101,6 @@
e: _|_ /* undefined field "t" */
}`),
}, {
- // a closed struct with template restrictions is exported as a
- // conjunction of two structs.
- in: `{
- A :: { b: int }
- a: A & { <_>: <10 }
- B :: a
- }`,
- out: unindent(`
- {
- A :: {
- b: int
- }
- a: close({
- b: <10
- }) & {
- <_>: <10
- }
- B :: {
- b: <10
- } & {
- <_>: <10
- }
- }`),
- }, {
in: `{
a: 5*[int]
a: [1, 2, ...]
@@ -383,8 +360,9 @@
}
}`),
}, {
- raw: true,
- eval: true,
+ raw: true,
+ eval: true,
+ noOpt: true,
in: `{
reg: { foo: 1, bar: { baz: 3 } }
def :: {
@@ -404,42 +382,38 @@
a: 1
sub: {
foo: 1
- bar: {
- baz: 3
- ...
- }
- ...
+ bar baz: 3
}
}
- val: close({
+ val: {
a: 1
sub: {
foo: 1
bar baz: 3
}
- })
+ }
}`),
}, {
raw: true,
eval: true,
in: `{
- b: [{
- <X>: int
- f: 4 if a > 4
- }][a]
- a: int
- c: *1 | 2
- }`,
+ b: [{
+ <X>: int
+ f: 4 if a > 4
+ }][a]
+ a: int
+ c: *1 | 2
+ }`,
// reference to a must be redirected to outer a through alias
out: unindent(`
- {
- b: [{
- <X>: int
- f: 4 if a > 4
- }][a]
- a: int
- c: 1
- }`),
+ {
+ b: [{
+ <X>: int
+ f: 4 if a > 4
+ }][a]
+ a: int
+ c: 1
+ }`),
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
@@ -455,7 +429,7 @@
n := root.(*structLit).arcs[0].v
v := newValueRoot(ctx, n)
- opts := options{raw: !tc.eval}
+ opts := options{raw: !tc.eval, omitOptional: true}
node, _ := export(ctx, v.eval(ctx), opts)
b, err := format.Node(node, format.Simplify())
if err != nil {
@@ -595,6 +569,67 @@
} | {
c: time.Duration
}`),
+ }, {
+ // a closed struct unified with a struct with a template restrictions is
+ // exported as a conjunction of two structs.
+ eval: true,
+ in: `
+ A :: { b: int }
+ a: A & { <_>: <10 }
+ B :: a
+ `,
+ out: unindent(`
+ {
+ A :: {
+ b: int
+ }
+ a: close({
+ b: <10
+ }) & {
+ <_>: <10
+ }
+ B :: {
+ b: <10
+ } & {
+ <_>: <10
+ }
+ }`),
+ }, {
+ eval: true,
+ in: `{
+ reg: { foo: 1, bar: { baz: 3 } }
+ def :: {
+ a: 1
+
+ sub: reg
+ }
+ val: def
+ }`,
+ out: unindent(`
+ {
+ reg: {
+ foo: 1
+ bar baz: 3
+ }
+ def :: {
+ a: 1
+ sub: {
+ foo: 1
+ bar: {
+ baz: 3
+ ...
+ }
+ ...
+ }
+ }
+ val: close({
+ a: 1
+ sub: {
+ foo: 1
+ bar baz: 3
+ }
+ })
+ }`),
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
@@ -603,7 +638,11 @@
if err != nil {
t.Fatal(err)
}
- b, err := format.Node(inst.Value().Syntax(Raw()), format.Simplify())
+ opts := []Option{}
+ if !tc.eval {
+ opts = []Option{Raw()}
+ }
+ b, err := format.Node(inst.Value().Syntax(opts...), format.Simplify())
if err != nil {
log.Fatal(err)
}
diff --git a/cue/value.go b/cue/value.go
index ed49e71..8ce8537 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -658,7 +658,7 @@
}
func (x *structLit) allows(f label) bool {
- return !x.isClosed
+ return !x.isClosed || f&hidden != 0
}
func newStruct(src source) *structLit {
@@ -886,7 +886,7 @@
// A label is a canonicalized feature name.
type label uint32
-const hidden label = 0x01 // only set iff identifier starting with $
+const hidden label = 0x01 // only set iff identifier starting with _
// An arc holds the label-value pair.
//
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 48393ff..453c77e 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -1122,7 +1122,7 @@
By default, structs are open to adding fields.
Instances of an open struct `p` may contain fields not defined in `p`.
-A _closed struct_ `c` is a struct whose instances may not have fields
+A _closed struct_ `c` is a struct whose instances may not have regular fields
not defined in `c`.
Closing a struct is equivalent to adding an optional field with value `_|_`
for all undefined fields.
@@ -1190,7 +1190,7 @@
or as a _definition_ (using `::`).
Definitions are not emitted as part of the model and are never required
to be concrete when emitting data.
-It is illegal to have a normal field and a definition with the same name
+It is illegal to have a regular field and a definition with the same name
within the same struct.
Literal structs that are part of a definition's value are implicitly closed.
An ellipsis `...` in such literal structs keeps them open,
@@ -1517,7 +1517,7 @@
All other identifiers are not exported.
An identifier that starts with the underscore "_" is not
-emitted in any data output.
+emitted in any data output and treated as a definition for that purpose.
Quoted labels that start with an underscore are emitted, however.
<!-- END REPLACE -->