Gopls v0.14 supports a new refactoring operation: inlining of function calls.

You can find it in VS Code by selecting a static call to a function or method f and choosing the Refactor... command followed by Inline call to f. Other editors and LSP clients have their own idiomatic command for it; for example, in Emacs with Eglot it is M-x eglot-code-action-inline and in Vim with coc.nvim it is coc-rename.

Before: select Refactor… Inline call to sum After: the call has been replaced by the sum logic

Inlining replaces the call expression by a copy of the function body, with parameters replaced by arguments. Inlining is useful for a number of reasons. Perhaps you want to eliminate a call to a deprecated function such as ioutil.ReadFile by replacing it with a call to the newer os.ReadFile; inlining will do that for you. Or perhaps you want to copy and modify an existing function in some way; inlining can provide a starting point. The inlining logic also provides a building block for other refactorings to come, such as “change signature”.

Not every call can be inlined. Of course, the tool needs to know which function is being called, so you can’t inline a dynamic call through a function value or interface method; but static calls to methods are fine. Nor can you inline a call if the callee is declared in another package and refers to non-exported parts of that package, or to internal packages that are inaccessible to the caller.

When inlining is possible, it’s critical that the tool preserve the original behavior of the program. We don’t want refactoring to break the build, or, worse, to introduce subtle latent bugs. This is especially important when inlining tools are used to perform automated clean-ups in large code bases. We must be able to trust the tool. Our inliner is very careful not to make guesses or unsound assumptions about the behavior of the code. However, that does mean it sometimes produces a change that differs from what someone with expert knowledge of the same code might have written by hand.

In the most difficult cases, especially with complex control flow, it may not be safe to eliminate the function call at all. For example, the behavior of a defer statement is intimately tied to its enclosing function call, and defer is the only control construct that can be used to handle panics, so it cannot be reduced into simpler constructs. So, for example, given a function f defined as:

func f(s string) {
    defer fmt.Println("goodbye")
    fmt.Println(s)
}

a call f("hello") will be inlined to:

    func() {
        defer fmt.Println("goodbye")
        fmt.Println("hello")
    }()

Although the parameter was eliminated, the function call remains.

An inliner is a bit like an optimizing compiler. A compiler is considered “correct” if it doesn’t change the meaning of the program in translation from source language to target language. An optimizing compiler exploits the particulars of the input to generate better code, where “better” usually means more efficient. As users report inputs that cause the compiler to emit suboptimal code, the compiler is improved to recognize more cases, or more rules, and more exceptions to rules—but this process has no end. Inlining is similar, except that “better” code means tidier code. The most conservative translation provides a simple but (hopefully!) correct foundation, on top of which endless rules, and exceptions to rules, can embellish and improve the quality of the output.

The following section lists some of the technical challenges involved in sound inlining:

This is just a taste of the problem domain. If you’re curious, the documentation for golang.org/x/tools/internal/refactor/inline has more detail. All of this is to say, it’s a complex problem, and we aim for correctness first of all. We’ve already implemented a number of important “tidiness optimizations” and we expect more to follow.

Please give the inliner a try, and if you find any bugs (where the transformation is incorrect), please do report them. We’d also like to hear what “optimizations” you’d like to see next.


The source files for this documentation can be found beneath golang.org/x/tools/gopls/doc.