Skip to content

cmd/fix: go fix does not inline nested inlines #78164

@thaJeztah

Description

@thaJeztah

Go version

go version go1.26.1 darwin/arm64

Output of go env in your module/workspace:

not relevant

What did you do?

I wasn't sure whether to report as a bug or enhancement, as this may be within the line of expectations, but I noticed that go fix fails to inline if an inline is an argument to a function/method that itself has to be inlined. If this happens within a package, the whole package is ignored; "inlines" that work in other packages are not applied.

Some things I tried to see what could make this work;

  • Putting the types to inline in separate files does not make a difference (so it seems the "skip" is at the package level).
  • Formatting the types to inline over multiple lines did not make a difference (this was testing if the "skip" was due to conflicts / adjacent lines).

I also tried if running go fix multiple times would help (as it sometimes would if applying multiple would result in a merge conflict), but unfortunately, that didn't work.

I'm not (yet) familiar with the implementation / logic under the hood for this feature, and maybe this is just a limitation to document, but also looked at the problem cases, and wondered if it could apply these changes in multiple passes (either automatically, or repeated go fix), for example;

Before:

client.NewWithOpts(client.FromEnv, client.WithLegacyOpt("x"))
client.NewWithOpts(client.FromEnv, client.WithLegacyOpt("x"), client.WithLegacyOpt2())

Pass 1:

client.NewWithOpts(client.FromEnv, client.WithOpt("x"))
client.NewWithOpts(client.FromEnv, client.WithOpt("x"), client.WithOpt2())

Pass 2:

client.New(client.FromEnv, client.WithOpt("x"))
client.New(client.FromEnv, client.WithOpt("x"), client.WithOpt2())

I created an example module that I used to test some of these scenarios (attached); some examples from that described below;

example.zip

(edit: attached the wrong example, and updated with extra example 😂 )

What did you see happen?

Examples

Given these //go:fix inline directives;

package client

type Client struct{}

type Opt func(*Client)

type CreateOpt struct{ Cmd []string }

//go:fix inline
type StrSlice = []string

func FromEnv(*Client) {}

//go:fix inline
func WithLegacyOpt(v string) Opt { return WithOpt(v) }
func WithOpt(string) Opt         { return nil }

//go:fix inline
func WithLegacyOpt2() Opt { return WithOpt2() }
func WithOpt2() Opt       { return nil }

//go:fix inline
func NewWithOpts(opts ...Opt) *Client { return New(opts...) }

func New(...Opt) *Client { return nil }

//go:fix inline
func (c *Client) CreateLegacy(o CreateOpt) { c.Create(o) }
func (c *Client) Create(CreateOpt)         {}

✅ These work

opts := []client.Opt{
    client.FromEnv,
    client.WithLegacyOpt("x"),
    client.WithLegacyOpt2(),
}

c := client.NewWithOpts(opts...)
c.Create(client.CreateOpt{Cmd: client.StrSlice{"top"}})

cmd := client.StrSlice{"top"}
c.CreateLegacy(client.CreateOpt{Cmd: cmd})
client.New(client.FromEnv, client.WithLegacyOpt("x"))
client.New(client.FromEnv, client.WithLegacyOpt("x"), client.WithLegacyOpt2())
client.NewWithOpts(client.FromEnv)
c := client.New(client.FromEnv)
c.Create(client.CreateOpt{Cmd: client.StrSlice{"top"}})

❌ These fail

client.NewWithOpts(client.FromEnv, client.WithLegacyOpt("x"))
client.NewWithOpts(client.FromEnv, client.WithLegacyOpt("x"), client.WithLegacyOpt2())

Combining a "failing" with a "non failing" inline will result in the whole package not being updated;

// OK works when in a separate package (see package "b"), but fails when in same package as [KO]
func OK() {
	client.New(client.FromEnv, client.WithLegacyOpt("x"))
}

// KO fails (due to nested inlines?), and causes whole package to be skipped.
func KO() {
	client.NewWithOpts(client.FromEnv, client.WithLegacyOpt("x"))
}

What did you expect to see?

I expected that repeated go fix runs could resolve this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions