Skip to content

Commit e2a352c

Browse files
committed
internal/refactor/inline: extensible API
This CL introduces Options and Result struct types to the public Inline function so that we can extend the input and outputs as needed. Options.IgnoreEffects allows a client to choose to ignore consideration of potential side effects of call arguments, an unsound "style optimization". Result.Literalized reports whether the chosen strategy was literalization. (Some clients may reject the transformation in that case.) A follow-up change will refine the API to separate the diff at the callsite from the logical diff to the import declaration. Updates golang/go#67049 Change-Id: Ifcec19d8cfd18fa797e16c7d6994ac916d77dab5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/581802 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com>
1 parent c16c816 commit e2a352c

File tree

6 files changed

+110
-37
lines changed

6 files changed

+110
-37
lines changed

gopls/internal/golang/inline.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ func inlineCall(ctx context.Context, snapshot *cache.Snapshot, callerPkg *cache.
113113
Content: callerPGF.Src,
114114
}
115115

116-
got, err := inline.Inline(logf, caller, callee)
116+
res, err := inline.Inline(caller, callee, &inline.Options{Logf: logf})
117117
if err != nil {
118118
return nil, nil, err
119119
}
120120

121121
return callerPkg.FileSet(), &analysis.SuggestedFix{
122122
Message: fmt.Sprintf("inline call of %v", callee),
123-
TextEdits: diffToTextEdits(callerPGF.Tok, diff.Bytes(callerPGF.Src, got)),
123+
TextEdits: diffToTextEdits(callerPGF.Tok, diff.Bytes(callerPGF.Src, res.Content)),
124124
}, nil
125125
}
126126

gopls/internal/golang/inline_all.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,11 @@ func inlineAllCalls(ctx context.Context, logf func(string, ...any), snapshot *ca
193193
Call: calls[currentCall],
194194
Content: content,
195195
}
196-
var err error
197-
content, err = inline.Inline(logf, caller, callee)
196+
res, err := inline.Inline(caller, callee, &inline.Options{Logf: logf})
198197
if err != nil {
199198
return nil, fmt.Errorf("inlining failed: %v", err)
200199
}
200+
content = res.Content
201201
if post != nil {
202202
content = post(content)
203203
}

internal/refactor/inline/analyzer/analyzer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,12 @@ func run(pass *analysis.Pass) (interface{}, error) {
126126
Call: call,
127127
Content: content,
128128
}
129-
got, err := inline.Inline(discard, caller, callee)
129+
res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard})
130130
if err != nil {
131131
pass.Reportf(call.Lparen, "%v", err)
132132
return
133133
}
134+
got := res.Content
134135

135136
// Suggest the "fix".
136137
var textEdits []analysis.TextEdit

internal/refactor/inline/everything_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,19 @@ func TestEverything(t *testing.T) {
173173
t.Fatal(err)
174174
}
175175

176-
got, err := inline.Inline(t.Logf, caller, callee)
176+
res, err := inline.Inline(caller, callee, &inline.Options{
177+
Logf: t.Logf,
178+
})
177179
if err != nil {
178180
// Write error to a log, but this ok.
179181
t.Log(err)
180182
return
181183
}
184+
got := res.Content
182185

183186
// Print the diff.
184187
t.Logf("Got diff:\n%s",
185-
diff.Unified("old", "new", string(callerContent), string(got)))
188+
diff.Unified("old", "new", string(callerContent), string(res.Content)))
186189

187190
// Parse and type-check the transformed source.
188191
f, err := parser.ParseFile(caller.Fset, callPosn.Filename, got, parser.SkipObjectResolution)

internal/refactor/inline/inline.go

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,54 @@ type Caller struct {
4040
enclosingFunc *ast.FuncDecl // top-level function/method enclosing the call, if any
4141
}
4242

43-
type unit struct{} // for representing sets as maps
43+
// Options specifies parameters affecting the inliner algorithm.
44+
// All fields are optional.
45+
type Options struct {
46+
Logf func(string, ...any) // log output function, records decision-making process
47+
IgnoreEffects bool // ignore potential side effects of arguments (unsound)
48+
}
49+
50+
// Result holds the result of code transformation.
51+
type Result struct {
52+
Content []byte // formatted, transformed content of caller file
53+
Literalized bool // chosen strategy replaced callee() with func(){...}()
54+
55+
// TODO(adonovan): provide an API for clients that want structured
56+
// output: a list of import additions and deletions plus one or more
57+
// localized diffs (or even AST transformations, though ownership and
58+
// mutation are tricky) near the call site.
59+
}
4460

4561
// Inline inlines the called function (callee) into the function call (caller)
4662
// and returns the updated, formatted content of the caller source file.
4763
//
4864
// Inline does not mutate any public fields of Caller or Callee.
49-
//
50-
// The log records the decision-making process.
51-
//
52-
// TODO(adonovan): provide an API for clients that want structured
53-
// output: a list of import additions and deletions plus one or more
54-
// localized diffs (or even AST transformations, though ownership and
55-
// mutation are tricky) near the call site.
56-
func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte, error) {
65+
func Inline(caller *Caller, callee *Callee, opts *Options) (*Result, error) {
66+
copy := *opts // shallow copy
67+
opts = &copy
68+
// Set default options.
69+
if opts.Logf == nil {
70+
opts.Logf = func(string, ...any) {}
71+
}
72+
73+
st := &state{
74+
caller: caller,
75+
callee: callee,
76+
opts: opts,
77+
}
78+
return st.inline()
79+
}
80+
81+
// state holds the working state of the inliner.
82+
type state struct {
83+
caller *Caller
84+
callee *Callee
85+
opts *Options
86+
}
87+
88+
func (st *state) inline() (*Result, error) {
89+
logf, caller, callee := st.opts.Logf, st.caller, st.callee
90+
5791
logf("inline %s @ %v",
5892
debugFormatNode(caller.Fset, caller.Call),
5993
caller.Fset.PositionFor(caller.Call.Lparen, false))
@@ -68,7 +102,7 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
68102
return nil, fmt.Errorf("cannot inline calls from files that import \"C\"")
69103
}
70104

71-
res, err := inline(logf, caller, &callee.impl)
105+
res, err := st.inlineCall()
72106
if err != nil {
73107
return nil, err
74108
}
@@ -304,15 +338,25 @@ func Inline(logf func(string, ...any), caller *Caller, callee *Callee) ([]byte,
304338
}
305339
newSrc = formatted
306340
}
307-
return newSrc, nil
341+
342+
literalized := false
343+
if call, ok := res.new.(*ast.CallExpr); ok && is[*ast.FuncLit](call.Fun) {
344+
literalized = true
345+
}
346+
347+
return &Result{
348+
Content: newSrc,
349+
Literalized: literalized,
350+
}, nil
351+
308352
}
309353

310354
type newImport struct {
311355
pkgName string
312356
spec *ast.ImportSpec
313357
}
314358

315-
type result struct {
359+
type inlineCallResult struct {
316360
newImports []newImport
317361
// If elideBraces is set, old is an ast.Stmt and new is an ast.BlockStmt to
318362
// be spliced in. This allows the inlining analysis to assert that inlining
@@ -329,9 +373,7 @@ type result struct {
329373
old, new ast.Node // e.g. replace call expr by callee function body expression
330374
}
331375

332-
type logger = func(string, ...any)
333-
334-
// inline returns a pair of an old node (the call, or something
376+
// inlineCall returns a pair of an old node (the call, or something
335377
// enclosing it) and a new node (its replacement, which may be a
336378
// combination of caller, callee, and new nodes), along with the set
337379
// of new imports needed.
@@ -350,7 +392,9 @@ type logger = func(string, ...any)
350392
// candidate for evaluating an alternative fully self-contained tree
351393
// representation, such as any proposed solution to #20744, or even
352394
// dst or some private fork of go/ast.)
353-
func inline(logf logger, caller *Caller, callee *gobCallee) (*result, error) {
395+
func (st *state) inlineCall() (*inlineCallResult, error) {
396+
logf, caller, callee := st.opts.Logf, st.caller, &st.callee.impl
397+
354398
checkInfoFields(caller.Info)
355399

356400
// Inlining of dynamic calls is not currently supported,
@@ -554,7 +598,7 @@ func inline(logf logger, caller *Caller, callee *gobCallee) (*result, error) {
554598
objRenames[i] = newName
555599
}
556600

557-
res := &result{
601+
res := &inlineCallResult{
558602
newImports: newImports,
559603
}
560604

@@ -582,7 +626,7 @@ func inline(logf logger, caller *Caller, callee *gobCallee) (*result, error) {
582626

583627
// Gather the effective call arguments, including the receiver.
584628
// Later, elements will be eliminated (=> nil) by parameter substitution.
585-
args, err := arguments(caller, calleeDecl, assign1)
629+
args, err := st.arguments(caller, calleeDecl, assign1)
586630
if err != nil {
587631
return nil, err // e.g. implicit field selection cannot be made explicit
588632
}
@@ -880,7 +924,7 @@ func inline(logf logger, caller *Caller, callee *gobCallee) (*result, error) {
880924
(!needBindingDecl || (bindingDecl != nil && len(bindingDecl.names) == 0)) {
881925

882926
// Reduces to: { var (bindings); lhs... := rhs... }
883-
if newStmts, ok := assignStmts(logf, caller, stmt, callee, results); ok {
927+
if newStmts, ok := st.assignStmts(stmt, results); ok {
884928
logf("strategy: reduce assign-context call to { return exprs }")
885929
clearPositions(calleeDecl.Body)
886930

@@ -1151,7 +1195,7 @@ type argument struct {
11511195
//
11521196
// We compute type-based predicates like pure, duplicable,
11531197
// freevars, etc, now, before we start modifying syntax.
1154-
func arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var) bool) ([]*argument, error) {
1198+
func (st *state) arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var) bool) ([]*argument, error) {
11551199
var args []*argument
11561200

11571201
callArgs := caller.Call.Args
@@ -1175,7 +1219,7 @@ func arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var
11751219
typ: caller.Info.TypeOf(recvArg),
11761220
constant: caller.Info.Types[recvArg].Value,
11771221
pure: pure(caller.Info, assign1, recvArg),
1178-
effects: effects(caller.Info, recvArg),
1222+
effects: st.effects(caller.Info, recvArg),
11791223
duplicable: duplicable(caller.Info, recvArg),
11801224
freevars: freeVars(caller.Info, recvArg),
11811225
}
@@ -1229,7 +1273,7 @@ func arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var
12291273
constant: tv.Value,
12301274
spread: is[*types.Tuple](tv.Type), // => last
12311275
pure: pure(caller.Info, assign1, expr),
1232-
effects: effects(caller.Info, expr),
1276+
effects: st.effects(caller.Info, expr),
12331277
duplicable: duplicable(caller.Info, expr),
12341278
freevars: freeVars(caller.Info, expr),
12351279
})
@@ -1911,7 +1955,7 @@ func freeishNames(free map[string]bool, t ast.Expr) {
19111955
// effects reports whether an expression might change the state of the
19121956
// program (through function calls and channel receives) and affect
19131957
// the evaluation of subsequent expressions.
1914-
func effects(info *types.Info, expr ast.Expr) bool {
1958+
func (st *state) effects(info *types.Info, expr ast.Expr) bool {
19151959
effects := false
19161960
ast.Inspect(expr, func(n ast.Node) bool {
19171961
switch n := n.(type) {
@@ -1939,6 +1983,15 @@ func effects(info *types.Info, expr ast.Expr) bool {
19391983
}
19401984
return true
19411985
})
1986+
1987+
// Even if consideration of effects is not desired,
1988+
// we continue to compute, log, and discard them.
1989+
if st.opts.IgnoreEffects && effects {
1990+
effects = false
1991+
st.opts.Logf("ignoring potential effects of argument %s",
1992+
debugFormatNode(st.caller.Fset, expr))
1993+
}
1994+
19421995
return effects
19431996
}
19441997

@@ -2766,7 +2819,9 @@ func declares(stmts []ast.Stmt) map[string]bool {
27662819
//
27672820
// Note: assignStmts may return (nil, true) if it determines that the rewritten
27682821
// assignment consists only of _ = nil assignments.
2769-
func assignStmts(logf logger, caller *Caller, callerStmt *ast.AssignStmt, callee *gobCallee, returnOperands []ast.Expr) ([]ast.Stmt, bool) {
2822+
func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Expr) ([]ast.Stmt, bool) {
2823+
logf, caller, callee := st.opts.Logf, st.caller, &st.callee.impl
2824+
27702825
assert(len(callee.Returns) == 1, "unexpected multiple returns")
27712826
resultInfo := callee.Returns[0]
27722827

@@ -3084,3 +3139,5 @@ func hasNonTrivialReturn(returnInfo [][]returnOperandFlags) bool {
30843139
}
30853140
return false
30863141
}
3142+
3143+
type unit struct{} // for representing sets as maps

internal/refactor/inline/inline_test.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func doInlineNote(logf func(string, ...any), pkg *packages.Package, file *ast.Fi
244244

245245
// Do the inlining. For the purposes of the test,
246246
// AnalyzeCallee and Inline are a single operation.
247-
got, err := func() ([]byte, error) {
247+
res, err := func() (*inline.Result, error) {
248248
filename := calleePkg.Fset.File(calleeDecl.Pos()).Name()
249249
content, err := os.ReadFile(filename)
250250
if err != nil {
@@ -267,7 +267,7 @@ func doInlineNote(logf func(string, ...any), pkg *packages.Package, file *ast.Fi
267267

268268
check := checkNoMutation(caller.File)
269269
defer check()
270-
return inline.Inline(logf, caller, callee)
270+
return inline.Inline(caller, callee, &inline.Options{Logf: logf})
271271
}()
272272
if err != nil {
273273
if wantRE, ok := want.(*regexp.Regexp); ok {
@@ -280,6 +280,7 @@ func doInlineNote(logf func(string, ...any), pkg *packages.Package, file *ast.Fi
280280
}
281281

282282
// Inline succeeded.
283+
got := res.Content
283284
if want, ok := want.([]byte); ok {
284285
got = append(bytes.TrimSpace(got), '\n')
285286
want = append(bytes.TrimSpace(want), '\n')
@@ -331,7 +332,7 @@ const funcName = "f"
331332
// strategy with the checklist of concerns enumerated in the package
332333
// doc comment.
333334
type testcase struct {
334-
descr string
335+
descr string // description; substrings enable options (e.g. "IgnoreEffects")
335336
callee, caller string // Go source files (sans package decl) of caller, callee
336337
want string // expected new portion of caller file, or "error: regexp"
337338
}
@@ -1223,6 +1224,12 @@ func TestSubstitutionPreservesArgumentEffectOrder(t *testing.T) {
12231224
`func _() { f(g(1), g(2), g(3)) }`,
12241225
`func _() { func() { defer println(any(g(1)), any(g(2)), g(3)) }() }`,
12251226
},
1227+
{
1228+
"Effects are ignored when IgnoreEffects",
1229+
`func f(x, y int) { println(y, x) }; func g(int) int`,
1230+
`func _() { f(g(1), g(2)) }`,
1231+
`func _() { println(g(2), g(1)) }`,
1232+
},
12261233
})
12271234
}
12281235

@@ -1417,7 +1424,7 @@ func runTests(t *testing.T, tests []testcase) {
14171424
}
14181425

14191426
// Analyze callee and inline call.
1420-
doIt := func() ([]byte, error) {
1427+
doIt := func() (*inline.Result, error) {
14211428
callee, err := inline.AnalyzeCallee(t.Logf, fset, pkg, info, decl, []byte(calleeContent))
14221429
if err != nil {
14231430
return nil, err
@@ -1436,9 +1443,12 @@ func runTests(t *testing.T, tests []testcase) {
14361443
}
14371444
check := checkNoMutation(caller.File)
14381445
defer check()
1439-
return inline.Inline(t.Logf, caller, callee)
1446+
return inline.Inline(caller, callee, &inline.Options{
1447+
Logf: t.Logf,
1448+
IgnoreEffects: strings.Contains(test.descr, "IgnoreEffects"),
1449+
})
14401450
}
1441-
gotContent, err := doIt()
1451+
res, err := doIt()
14421452

14431453
// Want error?
14441454
if rest := strings.TrimPrefix(test.want, "error: "); rest != test.want {
@@ -1459,6 +1469,8 @@ func runTests(t *testing.T, tests []testcase) {
14591469
t.Fatal(err)
14601470
}
14611471

1472+
gotContent := res.Content
1473+
14621474
// Compute a single-hunk line-based diff.
14631475
srcLines := strings.Split(callerContent, "\n")
14641476
gotLines := strings.Split(string(gotContent), "\n")

0 commit comments

Comments
 (0)