@@ -99,6 +99,40 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
99
99
res .new = & ast.ParenExpr {X : res .new .(ast.Expr )}
100
100
}
101
101
102
+ // Some reduction strategies return a new block holding the
103
+ // callee's statements. The block's braces may be elided when
104
+ // there is no conflict between names declared in the block
105
+ // with those declared by the parent block, and no risk of
106
+ // a caller's goto jumping forward across a declaration.
107
+ //
108
+ // This elision is only safe when the ExprStmt is beneath a
109
+ // BlockStmt, CaseClause.Body, or CommClause.Body;
110
+ // (see "statement theory").
111
+ elideBraces := false
112
+ if newBlock , ok := res .new .(* ast.BlockStmt ); ok {
113
+ parent := caller .path [nodeIndex (caller .path , res .old )+ 1 ]
114
+ var body []ast.Stmt
115
+ switch parent := parent .(type ) {
116
+ case * ast.BlockStmt :
117
+ body = parent .List
118
+ case * ast.CommClause :
119
+ body = parent .Body
120
+ case * ast.CaseClause :
121
+ body = parent .Body
122
+ }
123
+ if body != nil {
124
+ if len (callerLabels (caller .path )) > 0 {
125
+ // TODO(adonovan): be more precise and reject
126
+ // only forward gotos across the inlined block.
127
+ logf ("keeping block braces: caller uses control labels" )
128
+ } else if intersects (declares (newBlock .List ), declares (body )) {
129
+ logf ("keeping block braces: avoids name conflict" )
130
+ } else {
131
+ elideBraces = true
132
+ }
133
+ }
134
+ }
135
+
102
136
// Don't call replaceNode(caller.File, res.old, res.new)
103
137
// as it mutates the caller's syntax tree.
104
138
// Instead, splice the file, replacing the extent of the "old"
@@ -124,9 +158,16 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
124
158
// Precise comment handling would make this a
125
159
// non-issue. Formatting wouldn't really need a
126
160
// FileSet at all.
161
+ mark := out .Len ()
127
162
if err := format .Node (& out , caller .Fset , res .new ); err != nil {
128
163
return nil , err
129
164
}
165
+ if elideBraces {
166
+ // Overwrite unnecessary {...} braces with spaces.
167
+ // TODO(adonovan): less hacky solution.
168
+ out .Bytes ()[mark ] = ' '
169
+ out .Bytes ()[out .Len ()- 1 ] = ' '
170
+ }
130
171
out .Write (caller .Content [end :])
131
172
const mode = parser .ParseComments | parser .SkipObjectResolution | parser .AllErrors
132
173
f , err = parser .ParseFile (caller .Fset , "callee.go" , & out , mode )
@@ -630,7 +671,7 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
630
671
logf ("strategy: reduce call to empty body" )
631
672
632
673
// Evaluate the arguments for effects and delete the call entirely.
633
- stmt := callStmt (caller .path ) // cannot fail
674
+ stmt := callStmt (caller .path , false ) // cannot fail
634
675
res .old = stmt
635
676
if nargs := len (remainingArgs ); nargs > 0 {
636
677
// Emit "_, _ = args" to discard results.
@@ -862,9 +903,10 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
862
903
// - there is no label conflict between caller and callee
863
904
// - all parameters and result vars can be eliminated
864
905
// or replaced by a binding decl,
906
+ // - caller ExprStmt is in unrestricted statement context.
865
907
//
866
908
// If there is only a single statement, the braces are omitted.
867
- if stmt := callStmt (caller .path ); stmt != nil &&
909
+ if stmt := callStmt (caller .path , true ); stmt != nil &&
868
910
(! needBindingDecl || bindingDeclStmt != nil ) &&
869
911
! callee .HasDefer &&
870
912
! hasLabelConflict (caller .path , callee .Labels ) &&
@@ -876,7 +918,7 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
876
918
if needBindingDecl {
877
919
body .List = prepend (bindingDeclStmt , body .List ... )
878
920
}
879
- if len (body .List ) == 1 {
921
+ if len (body .List ) == 1 { // FIXME do this opt later
880
922
repl = body .List [0 ] // singleton: omit braces
881
923
}
882
924
res .old = stmt
@@ -2018,30 +2060,41 @@ func callContext(callPath []ast.Node) ast.Node {
2018
2060
// enclosing the call (specified as a PathEnclosingInterval)
2019
2061
// intersects with the set of callee labels.
2020
2062
func hasLabelConflict (callPath []ast.Node , calleeLabels []string ) bool {
2063
+ labels := callerLabels (callPath )
2064
+ for _ , label := range calleeLabels {
2065
+ if labels [label ] {
2066
+ return true // conflict
2067
+ }
2068
+ }
2069
+ return false
2070
+ }
2071
+
2072
+ // callerLabels returns the set of control labels in the function (if
2073
+ // any) enclosing the call (specified as a PathEnclosingInterval).
2074
+ func callerLabels (callPath []ast.Node ) map [string ]bool {
2021
2075
var callerBody * ast.BlockStmt
2022
2076
switch f := callerFunc (callPath ).(type ) {
2023
2077
case * ast.FuncDecl :
2024
2078
callerBody = f .Body
2025
2079
case * ast.FuncLit :
2026
2080
callerBody = f .Body
2027
2081
}
2028
- conflict := false
2082
+ var labels map [ string ] bool
2029
2083
if callerBody != nil {
2030
2084
ast .Inspect (callerBody , func (n ast.Node ) bool {
2031
2085
switch n := n .(type ) {
2032
2086
case * ast.FuncLit :
2033
2087
return false // prune traversal
2034
2088
case * ast.LabeledStmt :
2035
- for _ , label := range calleeLabels {
2036
- if label == n .Label .Name {
2037
- conflict = true
2038
- }
2089
+ if labels == nil {
2090
+ labels = make (map [string ]bool )
2039
2091
}
2092
+ labels [n .Label .Name ] = true
2040
2093
}
2041
2094
return true
2042
2095
})
2043
2096
}
2044
- return conflict
2097
+ return labels
2045
2098
}
2046
2099
2047
2100
// callerFunc returns the innermost Func{Decl,Lit} node enclosing the
@@ -2059,11 +2112,64 @@ func callerFunc(callPath []ast.Node) ast.Node {
2059
2112
// callStmt reports whether the function call (specified
2060
2113
// as a PathEnclosingInterval) appears within an ExprStmt,
2061
2114
// and returns it if so.
2062
- func callStmt (callPath []ast.Node ) * ast.ExprStmt {
2063
- stmt , _ := callContext (callPath ).(* ast.ExprStmt )
2115
+ //
2116
+ // If unrestricted, callStmt returns nil if the ExprStmt f() appears
2117
+ // in a restricted context (such as "if f(); cond {") where it cannot
2118
+ // be replaced by an arbitrary statement. (See "statement theory".)
2119
+ func callStmt (callPath []ast.Node , unrestricted bool ) * ast.ExprStmt {
2120
+ stmt , ok := callContext (callPath ).(* ast.ExprStmt )
2121
+ if ok && unrestricted {
2122
+ switch callPath [nodeIndex (callPath , stmt )+ 1 ].(type ) {
2123
+ case * ast.LabeledStmt ,
2124
+ * ast.BlockStmt ,
2125
+ * ast.CaseClause ,
2126
+ * ast.CommClause :
2127
+ // unrestricted
2128
+ default :
2129
+ // TODO(adonovan): handle restricted
2130
+ // XYZStmt.Init contexts (but not ForStmt.Post)
2131
+ // by creating a block around the if/for/switch:
2132
+ // "if f(); cond {" -> "{ stmts; if cond {"
2133
+
2134
+ return nil // restricted
2135
+ }
2136
+ }
2064
2137
return stmt
2065
2138
}
2066
2139
2140
+ // Statement theory
2141
+ //
2142
+ // These are all the places a statement may appear in the AST:
2143
+ //
2144
+ // LabeledStmt.Stmt Stmt -- any
2145
+ // BlockStmt.List []Stmt -- any (but see switch/select)
2146
+ // IfStmt.Init Stmt? -- simple
2147
+ // IfStmt.Body BlockStmt
2148
+ // IfStmt.Else Stmt? -- IfStmt or BlockStmt
2149
+ // CaseClause.Body []Stmt -- any
2150
+ // SwitchStmt.Init Stmt? -- simple
2151
+ // SwitchStmt.Body BlockStmt -- CaseClauses only
2152
+ // TypeSwitchStmt.Init Stmt? -- simple
2153
+ // TypeSwitchStmt.Assign Stmt -- AssignStmt(TypeAssertExpr) or ExprStmt(TypeAssertExpr)
2154
+ // TypeSwitchStmt.Body BlockStmt -- CaseClauses only
2155
+ // CommClause.Comm Stmt? -- SendStmt or ExprStmt(UnaryExpr) or AssignStmt(UnaryExpr)
2156
+ // CommClause.Body []Stmt -- any
2157
+ // SelectStmt.Body BlockStmt -- CommClauses only
2158
+ // ForStmt.Init Stmt? -- simple
2159
+ // ForStmt.Post Stmt? -- simple
2160
+ // ForStmt.Body BlockStmt
2161
+ // RangeStmt.Body BlockStmt
2162
+ //
2163
+ // simple = AssignStmt | SendStmt | IncDecStmt | ExprStmt.
2164
+ //
2165
+ // A BlockStmt cannot replace an ExprStmt in
2166
+ // {If,Switch,TypeSwitch}Stmt.Init or ForStmt.Post.
2167
+ // That is allowed only within:
2168
+ // LabeledStmt.Stmt Stmt
2169
+ // BlockStmt.List []Stmt
2170
+ // CaseClause.Body []Stmt
2171
+ // CommClause.Body []Stmt
2172
+
2067
2173
// replaceNode performs a destructive update of the tree rooted at
2068
2174
// root, replacing each occurrence of "from" with "to". If to is nil and
2069
2175
// the element is within a slice, the slice element is removed.
@@ -2372,13 +2478,7 @@ func consistentOffsets(caller *Caller) bool {
2372
2478
// ancestor of the CallExpr identified by its PathEnclosingInterval).
2373
2479
func needsParens (callPath []ast.Node , old , new ast.Node ) bool {
2374
2480
// Find enclosing old node and its parent.
2375
- // TODO(adonovan): Use index[ast.Node]() in go1.20.
2376
- i := - 1
2377
- for i = range callPath {
2378
- if callPath [i ] == old {
2379
- break
2380
- }
2381
- }
2481
+ i := nodeIndex (callPath , old )
2382
2482
if i == - 1 {
2383
2483
panic ("not found" )
2384
2484
}
@@ -2439,3 +2539,43 @@ func needsParens(callPath []ast.Node, old, new ast.Node) bool {
2439
2539
}
2440
2540
return false
2441
2541
}
2542
+
2543
+ func nodeIndex (nodes []ast.Node , n ast.Node ) int {
2544
+ // TODO(adonovan): Use index[ast.Node]() in go1.20.
2545
+ for i , node := range nodes {
2546
+ if node == n {
2547
+ return i
2548
+ }
2549
+ }
2550
+ return - 1
2551
+ }
2552
+
2553
+ // declares returns the set of lexical names declared by a
2554
+ // sequence of statements from the same block, excluding sub-blocks.
2555
+ // (Lexical names do not include control labels.)
2556
+ func declares (stmts []ast.Stmt ) map [string ]bool {
2557
+ names := make (map [string ]bool )
2558
+ for _ , stmt := range stmts {
2559
+ switch stmt := stmt .(type ) {
2560
+ case * ast.DeclStmt :
2561
+ for _ , spec := range stmt .Decl .(* ast.GenDecl ).Specs {
2562
+ switch spec := spec .(type ) {
2563
+ case * ast.ValueSpec :
2564
+ for _ , id := range spec .Names {
2565
+ names [id .Name ] = true
2566
+ }
2567
+ case * ast.TypeSpec :
2568
+ names [spec .Name .Name ] = true
2569
+ }
2570
+ }
2571
+
2572
+ case * ast.AssignStmt :
2573
+ if stmt .Tok == token .DEFINE {
2574
+ for _ , lhs := range stmt .Lhs {
2575
+ names [lhs .(* ast.Ident ).Name ] = true
2576
+ }
2577
+ }
2578
+ }
2579
+ }
2580
+ return names
2581
+ }
0 commit comments