@@ -222,136 +222,133 @@ func fixExplicitOpen(f *ast.File) (result *ast.File, hasChanges bool) {
222222// old semantics, conjuncts inserted through comprehensions were treated
223223// like embeddings and did not close their fields.
224224func openCompFieldValue (expr ast.Expr ) (ast.Expr , bool ) {
225+ // PostfixExpr is excluded: the value already has an ellipsis.
225226 switch expr .(type ) {
226- case * ast.SelectorExpr , * ast.IndexExpr :
227- return addEllipsis (expr ), true
228- case * ast.Ident , * ast.BinaryExpr , * ast.ParenExpr , * ast.CallExpr :
229- if _ , _ , f := collectEmbedFlags (expr ); f .mayBeClosed () {
227+ case * ast.Ident , * ast.SelectorExpr , * ast.IndexExpr ,
228+ * ast.BinaryExpr , * ast.ParenExpr , * ast.CallExpr :
229+ if collectEmbedFlags (expr ).mayBeClosed () {
230230 return addEllipsis (expr ), true
231231 }
232232 }
233233 return expr , false
234234}
235235
236236// collectEmbedFlags recurses into an expression to collect embedding flags
237- // without modifying the expression. Used for & and | where we add ... to the
238- // whole expression rather than individual operands.
239- func collectEmbedFlags (expr ast.Expr ) (ast.Expr , bool , embedFlags ) {
237+ // without modifying the expression. It is the single classifier of what an
238+ // embedded expression may resolve to; [openEmbedExpr] derives its rewrites
239+ // from the flags it returns.
240+ func collectEmbedFlags (expr ast.Expr ) embedFlags {
240241 switch x := expr .(type ) {
241242 case * ast.PostfixExpr :
242243 // Already has ellipsis (e.g. rewritten by a nested pass);
243244 // still collect flags from the underlying expression.
244245 if x .Op == token .ELLIPSIS {
245- _ , _ , f := collectEmbedFlags (x .X )
246- return expr , false , f
246+ return collectEmbedFlags (x .X )
247247 }
248248 case * ast.BinaryExpr :
249249 if x .Op == token .AND || x .Op == token .OR {
250- _ , _ , xf := collectEmbedFlags (x .X )
251- _ , _ , yf := collectEmbedFlags (x .Y )
250+ xf := collectEmbedFlags (x .X )
251+ yf := collectEmbedFlags (x .Y )
252252 f := xf .or (yf )
253253 if x .Op == token .OR && (xf .other || yf .other ) {
254254 f .forceReclose = true
255255 }
256- return expr , false , f
256+ return f
257257 }
258+ // Other binary ops (e.g. +, *) cannot resolve to closed structs.
259+ return embedFlags {}
258260 case * ast.ParenExpr :
259261 return collectEmbedFlags (x .X )
260262 case * ast.Ident :
261263 if x .Name == "_" {
262- return expr , false , embedFlags {}
264+ return embedFlags {}
263265 }
264266 if internal .IsDefinition (x ) {
265- return expr , false , embedFlags {def : true }
267+ return embedFlags {def : true }
266268 }
267- return expr , false , embedFlags {other : true }
269+ return embedFlags {other : true }
268270 case * ast.CallExpr :
269271 if id , ok := x .Fun .(* ast.Ident ); ok {
270272 switch id .Name {
271273 case "close" :
272- _ , _ , f := openCloseArg (x .Args [0 ])
273- return expr , false , f .or (embedFlags {close : true })
274+ f := embedFlags {close : true }
275+ if len (x .Args ) == 1 {
276+ _ , _ , af := openCloseArg (x .Args [0 ])
277+ f = f .or (af )
278+ }
279+ return f
280+ case "and" , "or" :
281+ return embedFlags {other : true }
274282 }
275283 }
284+ return embedFlags {}
285+ case * ast.UnaryExpr :
286+ // The default marker *X takes on X's closedness, but the
287+ // disjunction it appears in may resolve to another branch,
288+ // so the closing always needs a runtime check.
289+ if x .Op == token .MUL && collectEmbedFlags (x .X ).mayBeClosed () {
290+ return embedFlags {other : true }
291+ }
292+ return embedFlags {}
293+ case * ast.ListLit , // Lists cannot be opened anyway (atm).
294+ * ast.StructLit , // Structs are open by default.
295+ * ast.BasicLit ,
296+ * ast.Interpolation :
297+ return embedFlags {}
276298 }
277- return expr , false , embedFlags {}
299+
300+ // Default: may resolve to a closed struct (SelectorExpr, IndexExpr, etc.)
301+ return embedFlags {other : true }
278302}
279303
280- // openEmbedExpr adds postfix ellipsis to embedded expressions. For & and |
281- // expressions, it adds ... to the whole expression. For other expressions, it
282- // adds ellipsis if needed based on the expression type.
304+ // openEmbedExpr adds postfix ellipsis to embedded expressions, classifying
305+ // them via [collectEmbedFlags]. Conjunctions, disjunctions, and parenthesized
306+ // expressions always get ... on the whole expression; embedded close() calls
307+ // are hoisted; any other expression gets ... exactly when its flags indicate
308+ // it may resolve to a closed value.
283309func openEmbedExpr (expr ast.Expr ) (result ast.Expr , changed bool , flags embedFlags ) {
284310 switch x := expr .(type ) {
285311 case * ast.PostfixExpr :
286312 // Already has ellipsis; still collect flags from the underlying
287313 // expression, as they influence the wrapping of the enclosing
288314 // struct.
289- return collectEmbedFlags (x )
315+ return expr , false , collectEmbedFlags (x )
290316
291317 case * ast.BinaryExpr :
292- if x .Op == token .AND || x .Op == token .OR {
293- // Collect flags from operands, then add ... to the
294- // entire expression rather than each operand.
295- _ , _ , xFlags := collectEmbedFlags (x .X )
296- _ , _ , yFlags := collectEmbedFlags (x .Y )
297- f := xFlags .or (yFlags )
298- if x .Op == token .OR && (xFlags .other || yFlags .other ) {
299- f .forceReclose = true
300- }
301- return addEllipsis (expr ), true , f
318+ if x .Op != token .AND && x .Op != token .OR {
319+ // Other binary ops (e.g. +, *) don't need ellipsis.
320+ return expr , false , embedFlags {}
302321 }
303- // Other binary ops (e.g. +, *) don't need ellipsis.
304- return expr , false , embedFlags {}
322+ // Add ... to the entire expression rather than each operand,
323+ // even when no operand may resolve to a closed value.
324+ return addEllipsis (expr ), true , collectEmbedFlags (x )
305325
306326 case * ast.ParenExpr :
307- // Recurse through parens to collect flags, then add ...
308- // to the whole parenthesized expression.
309- _ , _ , f := collectEmbedFlags (x .X )
310- return addEllipsis (expr ), true , f
311-
312- case * ast.Ident :
313- if x .Name == "_" {
314- return expr , false , embedFlags {}
315- }
316- if internal .IsDefinition (x ) {
317- return addEllipsis (expr ), true , embedFlags {def : true }
318- }
319- return addEllipsis (expr ), true , embedFlags {other : true }
327+ // Add ... to the whole parenthesized expression.
328+ return addEllipsis (expr ), true , collectEmbedFlags (x )
320329
321330 case * ast.CallExpr :
322- if id , ok := x .Fun .(* ast.Ident ); ok {
323- switch id .Name {
324- case "close" :
325- // In the old semantics, embedding close() opened up
326- // the embedding — the outer struct stayed open. Under
327- // explicitopen, close() no longer opens up when embedded.
328- // Hoist close() to wrapper level: return the processed
329- // argument as the new embedding, and set the close flag
330- // so the containing struct gets close() wrapping.
331- if len (x .Args ) == 1 {
332- newArg , _ , f := openCloseArg (x .Args [0 ])
333- f .close = true
334- astutil .CopyMeta (newArg , x )
335- return newArg , true , f
336- }
337- return expr , true , embedFlags {close : true }
338- case "and" , "or" :
339- return addEllipsis (expr ), true , embedFlags {other : true }
331+ if id , ok := x .Fun .(* ast.Ident ); ok && id .Name == "close" {
332+ // In the old semantics, embedding close() opened up
333+ // the embedding — the outer struct stayed open. Under
334+ // explicitopen, close() no longer opens up when embedded.
335+ // Hoist close() to wrapper level: return the processed
336+ // argument as the new embedding, and set the close flag
337+ // so the containing struct gets close() wrapping.
338+ if len (x .Args ) == 1 {
339+ newArg , _ , f := openCloseArg (x .Args [0 ])
340+ f .close = true
341+ astutil .CopyMeta (newArg , x )
342+ return newArg , true , f
340343 }
344+ return expr , true , embedFlags {close : true }
341345 }
342- return expr , false , embedFlags {}
343-
344- case * ast.ListLit , // Lists cannot be opened anyway (atm).
345- * ast.StructLit , // Structs are open by default
346- * ast.BasicLit ,
347- * ast.Interpolation ,
348- * ast.UnaryExpr :
349-
350- return expr , false , embedFlags {}
351346 }
352347
353- // Default: needs ellipsis (SelectorExpr, IndexExpr, etc.)
354- return addEllipsis (expr ), true , embedFlags {other : true }
348+ if f := collectEmbedFlags (expr ); f .mayBeClosed () {
349+ return addEllipsis (expr ), true , f
350+ }
351+ return expr , false , embedFlags {}
355352}
356353
357354// openCloseArg processes the argument of an embedded close() call,
0 commit comments