golang.org/x/tools/gopls@v0.15.3/doc/refactor-inline.md (about)

     1  
     2  Gopls v0.14 supports a new refactoring operation:
     3  inlining of function calls.
     4  
     5  You can find it in VS Code by selecting a static call to a function or
     6  method f and choosing the `Refactor...` command followed by `Inline
     7  call to f`.
     8  Other editors and LSP clients have their own idiomatic command for it;
     9  for example, in Emacs with Eglot it is
    10  [`M-x eglot-code-action-inline`](https://joaotavora.github.io/eglot/#index-M_002dx-eglot_002dcode_002daction_002dinline)
    11  and in Vim with coc.nvim it is `coc-rename`.
    12  
    13  <!-- source code used for images:
    14  
    15  func six() int {
    16  	return sum(1, 2, 3)
    17  }
    18  
    19  func sum(values ...int) int {
    20  	total := 0
    21  	for _, v := range values {
    22  		total += v
    23  	}
    24  	return total
    25  }
    26  -->
    27  ![Before: select Refactor... Inline call to sum](inline-before.png)
    28  ![After: the call has been replaced by the sum logic](inline-after.png)
    29  
    30  Inlining replaces the call expression by a copy of the function body,
    31  with parameters replaced by arguments.
    32  Inlining is useful for a number of reasons.
    33  Perhaps you want to eliminate a call to a deprecated
    34  function such as `ioutil.ReadFile` by replacing it with a call to the
    35  newer `os.ReadFile`; inlining will do that for you.
    36  Or perhaps you want to copy and modify an existing function in some
    37  way; inlining can provide a starting point.
    38  The inlining logic also provides a building block for
    39  other refactorings to come, such as "change signature".
    40  
    41  Not every call can be inlined.
    42  Of course, the tool needs to know which function is being called, so
    43  you can't inline a dynamic call through a function value or interface
    44  method; but static calls to methods are fine.
    45  Nor can you inline a call if the callee is declared in another package
    46  and refers to non-exported parts of that package, or to [internal
    47  packages](https://go.dev/doc/go1.4#internalpackages) that are
    48  inaccessible to the caller.
    49  
    50  When inlining is possible, it's critical that the tool preserve
    51  the original behavior of the program.
    52  We don't want refactoring to break the build, or, worse, to introduce
    53  subtle latent bugs.
    54  This is especially important when inlining tools are used to perform
    55  automated clean-ups in large code bases.
    56  We must be able to trust the tool.
    57  Our inliner is very careful not to make guesses or unsound
    58  assumptions about the behavior of the code.
    59  However, that does mean it sometimes produces a change that differs
    60  from what someone with expert knowledge of the same code might have
    61  written by hand.
    62  
    63  In the most difficult cases, especially with complex control flow, it
    64  may not be safe to eliminate the function call at all.
    65  For example, the behavior of a `defer` statement is intimately tied to
    66  its enclosing function call, and `defer` is the only control
    67  construct that can be used to handle panics, so it cannot be reduced
    68  into simpler constructs.
    69  So, for example, given a function f defined as:
    70  
    71  ```go
    72  func f(s string) {
    73  	defer fmt.Println("goodbye")
    74  	fmt.Println(s)
    75  }
    76  ```
    77  a call `f("hello")` will be inlined to:
    78  ```go
    79  	func() {
    80  		defer fmt.Println("goodbye")
    81  		fmt.Println("hello")
    82  	}()
    83  ```
    84  Although the parameter was eliminated, the function call remains.
    85  
    86  An inliner is a bit like an optimizing compiler.
    87  A compiler is considered "correct" if it doesn't change the meaning of
    88  the program in translation from source language to target language.
    89  An _optimizing_ compiler exploits the particulars of the input to
    90  generate better code, where "better" usually means more efficient.
    91  As users report inputs that cause the compiler to emit suboptimal
    92  code, the compiler is improved to recognize more cases, or more rules,
    93  and more exceptions to rules---but this process has no end.
    94  Inlining is similar, except that "better" code means tidier code.
    95  The most conservative translation provides a simple but (hopefully!)
    96  correct foundation, on top of which endless rules, and exceptions to
    97  rules, can embellish and improve the quality of the output.
    98  
    99  The following section lists some of the technical
   100  challenges involved in sound inlining:
   101  
   102  - **Effects:** When replacing a parameter by its argument expression,
   103    we must be careful not to change the effects of the call. For
   104    example, if we call a function `func twice(x int) int { return x + x }`
   105    with `twice(g())`, we do not want to see `g() + g()`, which would
   106    cause g's effects to occur twice, and potentially each call might
   107    return a different value. All effects must occur the same number of
   108    times, and in the same order. This requires analyzing both the
   109    arguments and the callee function to determine whether they are
   110    "pure", whether they read variables, or whether (and when) they
   111    update them too. The inliner will introduce a declaration such as
   112    `var x int = g()` when it cannot prove that it is safe to substitute
   113    the argument throughout.
   114  
   115  - **Constants:** If inlining always replaced a parameter by its argument
   116    when the value is constant, some programs would no longer build
   117    because checks previously done at run time would happen at compile time.
   118    For example `func index(s string, i int) byte { return s[i] }`
   119    is a valid function, but if inlining were to replace the call `index("abc", 3)`
   120    by the expression `"abc"[3]`, the compiler will report that the
   121    index `3` is out of bounds for the string `"abc"`.
   122    The inliner will prevent substitution of parameters by problematic
   123    constant arguments, again introducing a `var` declaration instead.
   124  
   125  - **Referential integrity:** When a parameter variable is replaced by
   126    its argument expression, we must ensure that any names in the
   127    argument expression continue to refer to the same thing---not to a
   128    different declaration in the callee function body that happens to
   129    use the same name! The inliner must replace local references such as
   130    `Printf` by qualified references such as `fmt.Printf`, and add an
   131    import of package `fmt` as needed.
   132  
   133  - **Implicit conversions:** When passing an argument to a function, it
   134    is implicitly converted to the parameter type.
   135    If we eliminate the parameter variable, we don't want to
   136    lose the conversion as it may be important.
   137    For example, in `func f(x any) { y := x; fmt.Printf("%T", &y) }` the
   138    type of variable y is `any`, so the program prints `"*interface{}"`.
   139    But if inlining the call `f(1)` were to produce the statement `y :=
   140    1`, then the type of y would have changed to `int`, which could
   141    cause a compile error or, as in this case, a bug, as the program
   142    now prints `"*int"`. When the inliner substitutes a parameter variable
   143    by its argument value, it may need to introduce explicit conversions
   144    of each value to the original parameter type, such as `y := any(1)`.
   145  
   146  - **Last reference:** When an argument expression has no effects
   147    and its corresponding parameter is never used, the expression
   148    may be eliminated. However, if the expression contains the last
   149    reference to a local variable at the caller, this may cause a compile
   150    error because the variable is now unused! So the inliner must be
   151    cautious about eliminating references to local variables.
   152  
   153  This is just a taste of the problem domain. If you're curious, the
   154  documentation for [golang.org/x/tools/internal/refactor/inline](https://pkg.go.dev/golang.org/x/tools/internal/refactor/inline) has
   155  more detail. All of this is to say, it's a complex problem, and we aim
   156  for correctness first of all. We've already implemented a number of
   157  important "tidiness optimizations" and we expect more to follow.
   158  
   159  Please give the inliner a try, and if you find any bugs (where the
   160  transformation is incorrect), please do report them. We'd also like to
   161  hear what "optimizations" you'd like to see next.