@@ -310,7 +310,7 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
310
310
logf = func (string , ... any ) {} // discard
311
311
}
312
312
logf ("inline %s @ %v" ,
313
- formatNode (caller .Fset , caller .Call ),
313
+ debugFormatNode (caller .Fset , caller .Call ),
314
314
caller .Fset .Position (caller .Call .Lparen ))
315
315
316
316
res , err := inline (logf , caller , & callee .impl )
@@ -331,17 +331,19 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
331
331
{
332
332
start := offsetOf (caller .Fset , res .old .Pos ())
333
333
end := offsetOf (caller .Fset , res .old .End ())
334
+ var out bytes.Buffer
335
+ out .Write (caller .Content [:start ])
334
336
// TODO(adonovan): might it make more sense to use
335
337
// callee.Fset when formatting res.new??
336
- newFile := string (caller .Content [:start ]) +
337
- formatNode (caller .Fset , res .new ) +
338
- string (caller .Content [end :])
338
+ if err := format .Node (& out , caller .Fset , res .new ); err != nil {
339
+ return nil , err
340
+ }
341
+ out .Write (caller .Content [end :])
339
342
const mode = parser .ParseComments | parser .SkipObjectResolution | parser .AllErrors
340
- var err error
341
- f , err = parser .ParseFile (caller .Fset , "callee.go" , newFile , mode )
343
+ f , err = parser .ParseFile (caller .Fset , "callee.go" , & out , mode )
342
344
if err != nil {
343
345
// Something has gone very wrong.
344
- logf ("failed to parse <<%s>>" , newFile ) // debugging
346
+ logf ("failed to parse <<%s>>" , & out ) // debugging
345
347
return nil , err
346
348
}
347
349
}
@@ -376,6 +378,12 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
376
378
}
377
379
}
378
380
381
+ var out bytes.Buffer
382
+ if err := format .Node (& out , caller .Fset , f ); err != nil {
383
+ return nil , err
384
+ }
385
+ newSrc := out .Bytes ()
386
+
379
387
// Remove imports that are no longer referenced.
380
388
//
381
389
// It ought to be possible to compute the set of PkgNames used
@@ -425,16 +433,17 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
425
433
// We could invoke imports.Process and parse its result,
426
434
// compare against the original AST, compute a list of import
427
435
// fixes, and return that too.
428
- var out bytes.Buffer
429
- if err := format .Node (& out , caller .Fset , f ); err != nil {
430
- return nil , err
431
- }
432
- formatted , err := imports .Process ("output" , out .Bytes (), nil )
433
- if err != nil {
434
- logf ("cannot reformat: %v <<%s>>" , err , & out )
435
- return nil , err // cannot reformat (a bug?)
436
+
437
+ // Recompute imports only if there were existing ones.
438
+ if len (f .Imports ) > 0 {
439
+ formatted , err := imports .Process ("output" , newSrc , nil )
440
+ if err != nil {
441
+ logf ("cannot reformat: %v <<%s>>" , err , & out )
442
+ return nil , err // cannot reformat (a bug?)
443
+ }
444
+ newSrc = formatted
436
445
}
437
- return formatted , nil
446
+ return newSrc , nil
438
447
}
439
448
440
449
type result struct {
@@ -462,6 +471,8 @@ type result struct {
462
471
// representation, such as any proposed solution to #20744, or even
463
472
// dst or some private fork of go/ast.)
464
473
func inline (logf func (string , ... any ), caller * Caller , callee * gobCallee ) (* result , error ) {
474
+ checkInfoFields (caller .Info )
475
+
465
476
// Inlining of dynamic calls is not currently supported,
466
477
// even for local closure calls. (This would be a lot of work.)
467
478
calleeSymbol := typeutil .StaticCallee (caller .Info , caller .Call )
@@ -632,9 +643,10 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
632
643
}
633
644
634
645
// replaceCalleeID replaces an identifier in the callee.
646
+ // The replacement tree must not belong to the caller; use cloneNode as needed.
635
647
replaceCalleeID := func (offset int , repl ast.Expr ) {
636
648
id := findIdent (calleeDecl , calleeDecl .Pos ()+ token .Pos (offset ))
637
- logf ("- replace id %q @ #%d to %q" , id .Name , offset , formatNode (calleeFset , repl ))
649
+ logf ("- replace id %q @ #%d to %q" , id .Name , offset , debugFormatNode (calleeFset , repl ))
638
650
replaceNode (calleeDecl , id , repl )
639
651
}
640
652
@@ -812,12 +824,12 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
812
824
if is [* types.Tuple ](arg .typ ) {
813
825
// TODO(adonovan): handle elimination of spread arguments.
814
826
logf ("keeping param %q: argument %s is spread" ,
815
- param .Name , formatNode (caller .Fset , arg .expr ))
816
- continue
827
+ param .Name , debugFormatNode (caller .Fset , arg .expr ))
828
+ break // spread => last argument, but not last parameter
817
829
}
818
830
if ! arg .pure {
819
831
logf ("keeping param %q: argument %s is impure" ,
820
- param .Name , formatNode (caller .Fset , arg .expr ))
832
+ param .Name , debugFormatNode (caller .Fset , arg .expr ))
821
833
continue // unsafe to change order or cardinality of effects
822
834
}
823
835
if len (param .Refs ) > 1 && ! arg .duplicable {
@@ -872,10 +884,13 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
872
884
// It is safe to eliminate param and replace it with arg.
873
885
// No additional parens are required around arg for
874
886
// the supported "pure" expressions.
887
+ //
888
+ // Because arg.expr belongs to the caller,
889
+ // we clone it before splicing it into the callee tree.
875
890
logf ("replacing parameter %q by argument %q" ,
876
- param .Name , formatNode (caller .Fset , arg .expr ))
891
+ param .Name , debugFormatNode (caller .Fset , arg .expr ))
877
892
for _ , ref := range param .Refs {
878
- replaceCalleeID (ref , arg .expr )
893
+ replaceCalleeID (ref , cloneNode ( arg .expr ).(ast. Expr ) )
879
894
}
880
895
eliminatedParams [i ] = true
881
896
args [i ] = nil
@@ -889,6 +904,19 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
889
904
}
890
905
}
891
906
907
+ // TODO(adonovan): eliminate all remaining parameters
908
+ // by replacing a call f(a1, a2)
909
+ // to func f(x T1, y T2) {body} by
910
+ // { var x T1 = a1
911
+ // var y T2 = a2
912
+ // body }
913
+ // if x ∉ freevars(a2) or freevars(T2), and so on,
914
+ // plus the usual checks for return conversions (if any),
915
+ // complex control, etc.
916
+ //
917
+ // If viable, use this with the reduction strategies below
918
+ // that produce a block (not a value).
919
+
892
920
// -- let the inlining strategies begin --
893
921
894
922
// TODO(adonovan): split this huge function into a sequence of
@@ -1044,9 +1072,9 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
1044
1072
// Inlining:
1045
1073
// return f(args)
1046
1074
// where:
1047
- // func f(params) (results) { ... body... }
1075
+ // func f(params) (results) { body }
1048
1076
// reduces to:
1049
- // ... body...
1077
+ // { body }
1050
1078
// so long as:
1051
1079
// - all parameters are eliminated;
1052
1080
// - call is a tail-call;
@@ -1059,6 +1087,10 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
1059
1087
//
1060
1088
// TODO(adonovan): omit the braces if the sets of
1061
1089
// names in the two blocks are disjoint.
1090
+ //
1091
+ // TODO(adonovan): add a strategy for a 'void tail
1092
+ // call', i.e. a call statement prior to an (explicit
1093
+ // or implicit) return.
1062
1094
if ret , ok := callContext (callerPath ).(* ast.ReturnStmt ); ok &&
1063
1095
len (ret .Results ) == 1 &&
1064
1096
callee .TrivialReturns == callee .TotalReturns &&
@@ -1122,15 +1154,21 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
1122
1154
// in addition to the usual checks for arg/result conversions,
1123
1155
// complex control, etc.
1124
1156
// Also test cases where expr is an n-ary call (spread returns).
1157
+ }
1125
1158
1126
- // TODO(adonovan): replace a call f(a1, a2)
1127
- // to func f(x T1, y T2) {body} by
1128
- // { var x T1 = a1
1129
- // var y T2 = a2
1130
- // body }
1131
- // if x ∉ freevars(a2) or freevars(T2), and so on,
1132
- // plus the usual checks for return conversions (if any),
1133
- // complex control, etc.
1159
+ // Literalization isn't quite infallible.
1160
+ // Consider a spread call to a method in which
1161
+ // no parameters are eliminated, e.g.
1162
+ // new(T).f(g())
1163
+ // where
1164
+ // func (recv *T) f(x, y int) { body }
1165
+ // func g() (int, int)
1166
+ // This would be literalized to:
1167
+ // func (recv *T, x, y int) { body }(new(T), g()),
1168
+ // which is not a valid argument list because g() must appear alone.
1169
+ // Reject this case for now.
1170
+ if len (args ) == 2 && args [0 ] != nil && args [1 ] != nil && is [* types.Tuple ](args [1 ].typ ) {
1171
+ return nil , fmt .Errorf ("can't yet inline spread call to method" )
1134
1172
}
1135
1173
1136
1174
// Infallible general case: literalization.
@@ -1547,6 +1585,54 @@ func replaceNode(root ast.Node, from, to ast.Node) {
1547
1585
}
1548
1586
}
1549
1587
1588
+ // cloneNode returns a deep copy of a Node.
1589
+ // It omits pointers to ast.{Scope,Object} variables.
1590
+ func cloneNode (n ast.Node ) ast.Node {
1591
+ var clone func (x reflect.Value ) reflect.Value
1592
+ clone = func (x reflect.Value ) reflect.Value {
1593
+ switch x .Kind () {
1594
+ case reflect .Ptr :
1595
+ if x .IsNil () {
1596
+ return x
1597
+ }
1598
+ // Skip fields of types potentially involved in cycles.
1599
+ switch x .Interface ().(type ) {
1600
+ case * ast.Object , * ast.Scope :
1601
+ return reflect .Zero (x .Type ())
1602
+ }
1603
+ y := reflect .New (x .Type ().Elem ())
1604
+ y .Elem ().Set (clone (x .Elem ()))
1605
+ return y
1606
+
1607
+ case reflect .Struct :
1608
+ y := reflect .New (x .Type ()).Elem ()
1609
+ for i := 0 ; i < x .Type ().NumField (); i ++ {
1610
+ y .Field (i ).Set (clone (x .Field (i )))
1611
+ }
1612
+ return y
1613
+
1614
+ case reflect .Slice :
1615
+ y := reflect .MakeSlice (x .Type (), x .Len (), x .Cap ())
1616
+ for i := 0 ; i < x .Len (); i ++ {
1617
+ y .Index (i ).Set (clone (x .Index (i )))
1618
+ }
1619
+ return y
1620
+
1621
+ case reflect .Interface :
1622
+ y := reflect .New (x .Type ()).Elem ()
1623
+ y .Set (clone (x .Elem ()))
1624
+ return y
1625
+
1626
+ case reflect .Array , reflect .Chan , reflect .Func , reflect .Map , reflect .UnsafePointer :
1627
+ panic (x ) // unreachable in AST
1628
+
1629
+ default :
1630
+ return x // bool, string, number
1631
+ }
1632
+ }
1633
+ return clone (reflect .ValueOf (n )).Interface ().(ast.Node )
1634
+ }
1635
+
1550
1636
// clearPositions destroys token.Pos information within the tree rooted at root,
1551
1637
// as positions in callee trees may cause caller comments to be emitted prematurely.
1552
1638
//
@@ -1606,7 +1692,9 @@ func prepend[T any](elem T, slice ...T) []T {
1606
1692
return append ([]T {elem }, slice ... )
1607
1693
}
1608
1694
1609
- func formatNode (fset * token.FileSet , n ast.Node ) string {
1695
+ // debugFormatNode formats a node or returns a formatting error.
1696
+ // Its sloppy treatment of errors is appropriate only for logging.
1697
+ func debugFormatNode (fset * token.FileSet , n ast.Node ) string {
1610
1698
var out strings.Builder
1611
1699
if err := format .Node (& out , fset , n ); err != nil {
1612
1700
out .WriteString (err .Error ())
0 commit comments