Skip to content

Commit 8894549

Browse files
committed
tools/fix: open field values inside comprehensions
Under the old semantics, conjuncts inserted through comprehensions are treated like embeddings and do not close their fields, recursively. Under explicitopen this opening is gone, so a comprehension conjunct such as "if c { egress: #HC }" closes egress and rejects sibling entries added elsewhere. This broke the greymatter tenant-examples unity project, whose #Service inserts a health-check egress listener through a comprehension. Append a postfix ellipsis to field values inside comprehension bodies when they may resolve to a closed struct: references, selectors, index expressions, and conjunctions or disjunctions involving them. The postfix ellipsis opens recursively, matching the old comprehension behavior. Values that cannot carry closedness, such as literals, are left alone, as are close() calls, which explicitly opt in to closing. The new lexical comprehension counter subsumes the field-scoped closeInfo.inComprehension, whose only use was the TODO comment on embeddings; that comment now also applies to embeddings nested under fields within a comprehension. Signed-off-by: Daniel Martí <mvdan@mvdan.cc> Change-Id: Ia1498de6ed63412cee0266edd6094d37aa4bff47 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1242590 TryBot-Result: CUEcueckoo <cueckoo@cuelang.org> Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com> Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
1 parent 468e553 commit 8894549

2 files changed

Lines changed: 43 additions & 11 deletions

File tree

tools/fix/fix_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,6 @@ v: __closeAll({
176176
// comprehensions are treated like embeddings and do not close
177177
// their fields. Field values that may resolve to closed
178178
// structs must be opened to preserve that behavior.
179-
// TODO: the egress value is left untouched; it should become
180-
// "#HC..." so that sibling entries of egress remain allowed.
181179
name: "open field values in comprehensions (fixExplicitOpen)",
182180
exps: []string{"explicitopen"},
183181
in: `package foo
@@ -192,15 +190,17 @@ v: __closeAll({
192190
}
193191
}
194192
`,
195-
out: `package foo
193+
out: `@experiment(explicitopen)
194+
195+
package foo
196196
197197
#HC: hc: {port: 1}
198198
199199
#Service: {
200200
enable: bool
201201
egress?: [string]: {...}
202202
if enable {
203-
egress: #HC
203+
egress: #HC...
204204
}
205205
}
206206
`,

tools/fix/fixopen.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@ func (a embedFlags) or(b embedFlags) embedFlags {
4545
}
4646
}
4747

48+
// mayBeClosed reports whether the expression the flags were collected
49+
// from may resolve to a closed value.
50+
func (f embedFlags) mayBeClosed() bool {
51+
return f.def || f.other || f.close
52+
}
53+
4854
type closeInfo struct {
4955
// Do not close enclosing structs if non-zero. This may be the case
5056
// for comprehensions, nested structs, etc.
5157
suspendReclose int
5258

53-
// inComprehension tracks whether we are inside a comprehension value.
54-
inComprehension int
55-
5659
embedFlags
5760
}
5861

@@ -64,6 +67,10 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
6467

6568
var info closeInfo
6669
recloseStack := []closeInfo{}
70+
// comprehensionDepth tracks whether we are lexically inside a
71+
// comprehension value. Unlike the closeInfo state, it is not reset
72+
// at field or conjunction boundaries.
73+
comprehensionDepth := 0
6774
result = astutil.Apply(f, func(c astutil.Cursor) bool {
6875
n := c.Node()
6976
switch n := n.(type) {
@@ -82,7 +89,7 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
8289

8390
case *ast.Comprehension:
8491
info.suspendReclose++
85-
info.inComprehension++
92+
comprehensionDepth++
8693

8794
case *ast.EmbedDecl:
8895
info.suspendReclose++
@@ -95,6 +102,15 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
95102
recloseStack = recloseStack[:len(recloseStack)-1]
96103
c.ClearEnclosingModified()
97104

105+
// See openCompFieldValue: comprehension conjuncts did not
106+
// close their fields under the old semantics.
107+
if comprehensionDepth > 0 {
108+
if newValue, changed := openCompFieldValue(n.Value); changed {
109+
n.Value = newValue
110+
hasChanges = true
111+
}
112+
}
113+
98114
case *ast.BinaryExpr:
99115
if n.Op == token.AND || n.Op == token.OR {
100116
info = recloseStack[len(recloseStack)-1]
@@ -104,7 +120,7 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
104120

105121
case *ast.Comprehension:
106122
info.suspendReclose--
107-
info.inComprehension--
123+
comprehensionDepth--
108124

109125
case *ast.EmbedDecl:
110126
info.suspendReclose--
@@ -116,7 +132,7 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
116132
newExpr, exprChanged, flags := openEmbedExpr(n.Expr)
117133
info.embedFlags = info.embedFlags.or(flags)
118134
if exprChanged {
119-
if info.inComprehension > 0 {
135+
if comprehensionDepth > 0 {
120136
ast.AddComment(newExpr, todoComment(
121137
"... may not be intended inside a comprehension value; consider removing it."))
122138
}
@@ -185,6 +201,22 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
185201
return result, hasChanges
186202
}
187203

204+
// openCompFieldValue adds a postfix ellipsis to a field value inside a
205+
// comprehension when the value may resolve to a closed struct. Under the
206+
// old semantics, conjuncts inserted through comprehensions were treated
207+
// like embeddings and did not close their fields.
208+
func openCompFieldValue(expr ast.Expr) (ast.Expr, bool) {
209+
switch expr.(type) {
210+
case *ast.SelectorExpr, *ast.IndexExpr:
211+
return addEllipsis(expr), true
212+
case *ast.Ident, *ast.BinaryExpr, *ast.ParenExpr:
213+
if _, _, f := collectEmbedFlags(expr); f.mayBeClosed() {
214+
return addEllipsis(expr), true
215+
}
216+
}
217+
return expr, false
218+
}
219+
188220
// collectEmbedFlags recurses into an expression to collect embedding flags
189221
// without modifying the expression. Used for & and | where we add ... to the
190222
// whole expression rather than individual operands.
@@ -316,7 +348,7 @@ func openCloseArg(expr ast.Expr) (ast.Expr, bool, embedFlags) {
316348
// Non-struct argument: process like a regular embedding so that
317349
// e.g. close(#A) → #A... when hoisted.
318350
newExpr, _, f := openEmbedExpr(expr)
319-
return newExpr, f.def || f.other || f.close, f
351+
return newExpr, f.mayBeClosed(), f
320352
}
321353
var f embedFlags
322354
var changed bool

0 commit comments

Comments
 (0)