Description

Proposal to add a code action that refactors short variable declarations (:=) into explicit var declarations with separate assignment.

Example:

// Before
f := os.DirFS("/")

// After
var f fs.FS
f = os.DirFS("/")

Motivation

Suppose we start with the following code:

obj := info.ObjectOf(t)

Later, we realize that the value cannot be initialized at the point of declaration, so we want to refactor the code something like this:

var obj types.Object
switch t := fnval.(type) {
case *ast.Ident:
    obj = info.ObjectOf(t)
case *ast.SelectorExpr:
    obj = info.ObjectOf(t.Sel)
}

To rewrite this manually, we need to determine the type of obj - by hovering over it or navigating to its definition and copying the type.

A code action could infer the type for the var declaration directly from the right-hand side of the original := assignment, streamlining the refactoring process.

Concerns and Open Questions

There are a few edge cases where behavior is less clear or might need refinement:

  1. Multiple assignments
// Extract each individually
//
// var n int
// var err error
// n, err = fmt.Fprintln(os.Stdout)
n, err := fmt.Fprintln(os.Stdout)
  1. Shadowed variables
n1, err := fmt.Fprintln(os.Stdout)

// Keep shadowing
//
// var n2 int
// n2, err = fmt.Fprintln(os.Stdout)
n2, err := fmt.Fprintln(os.Stdout)
  1. Missing imports
// Should add import for fs.FS
//
// var f fs.FS
// f = os.DirFS("/")
f := os.DirFS("/")
  1. Unexported types

In some cases, the inferred type may be an unexported type from another package. Since such types cannot be named outside their defining package, there's no valid 'var' declaration that can reference them. In these situations, the code action should be suppressed, as there is no meaningful or compilable transformation.

I'd be happy to receive feedback, discuss expected behavior in more detail, and work on the implementation if this feature is considered useful and aligned with gopls's direction.

Comment From: adonovan

A "Replace := with var declaration` code action seems reasonable to me, as does the converse ("Replace var with := declaration"). Feel free to send a CL; study previous CLs that add a CodeActionKind to find the set of places that need to be change (including docs, tests and release notes). Thanks.

Comment From: jakebailey

It's interesting to want the := to var direction; both gofumpt and staticcheck convert as many vars as possible to :=.

Comment From: skewb1k

@jakebailey It's mainly for refactoring purposes - for example, when a := assignment needs to be split into an var declaration with 0-value initialization and assigns within case clauses or other branching. So the final result wouldn't be convertible back to :=.

The key thing gopls can help with here is extracting the type for the var statement from the RHS expression.

Comment From: skewb1k

Updated the original comment with a clearer example of use-case.