Container Orchestration

2026-05-13 17:18:16

Modernizing Go Code with //go:fix and Source-Level Inlining

Explore Go 1.26's source-level inliner, how it powers //go:fix inline for self-service API migrations, and its role in modernizing codebases safely.

Introduction

The Go 1.26 release introduces a completely revamped go fix subcommand, designed to help developers keep their codebases up to date and modern. Among its new capabilities is the source-level inliner—a powerful tool that goes beyond traditional compiler optimizations. This article explores how the source-level inliner works, how it integrates with go fix, and how it enables package authors to create self-service API migrations using the //go:fix inline directive.

Modernizing Go Code with //go:fix and Source-Level Inlining
Source: blog.golang.org

What Is Source-Level Inlining?

Inlining is a classic compiler optimization that replaces a function call with a copy of the function’s body, substituting actual arguments for formal parameters. In Go compilers, this transformation happens on an internal representation to produce faster machine code. Source-level inlining, however, performs the same substitution directly on the source code, producing durable changes that you can commit to your repository.

If you’ve ever used the “Inline call” refactoring in gopls (the Go language server), you’ve already experienced source-level inlining. In VS Code, for example, you can find this action under the “Source Action…” menu. The transformation replaces, say, a call to sum inside a function six with the actual arithmetic, making the code clearer and sometimes more efficient.

How the Source-Level Inliner Works

The inliner takes a function call expression and replaces it with a modified copy of the called function’s body. This involves:

  • Mapping the call’s arguments to the function’s parameters.
  • Copying the function body’s AST (Abstract Syntax Tree).
  • Performing variable renaming to avoid naming conflicts.
  • Handling side effects, such as argument evaluation order.

For example, consider a function func sum(a, b int) int { return a + b } called from six(). Inlining replaces sum(1, 5) with 1 + 5. The before-and-after transformation is straightforward in simple cases, but the algorithm also handles complex scenarios like closures, multiple return values, and type parameters.

Relation to gopls Refactorings

The source-level inliner is a key building block for several gopls refactorings, including “Change signature” and “Remove unused parameter.” These operations need to rewrite function bodies and call sites without breaking the program. By reusing the inliner, maintainers ensure that all correctness constraints—such as preserving evaluation order and avoiding variable capture—are satisfied automatically.

//go:fix inline: Self-Service API Migrations

The true innovation in Go 1.26 is deploying the source-level inliner as part of the new go fix command. Package authors can now annotate functions with the //go:fix inline directive to indicate that all calls to that function should be inlined when a user runs go fix.

This turns the inliner into a self-service migration tool. For example, if a library deprecates a helper function and provides a better alternative, the author can mark the old function with //go:fix inline. When users upgrade the library and run go fix, every call to the deprecated function is automatically replaced with the inlined body, often revealing simpler patterns or enabling further manual cleanup.

Modernizing Go Code with //go:fix and Source-Level Inlining
Source: blog.golang.org

Example of a Migration

Suppose an HTTP package had a function mustBind(r *http.Request, v interface{}) that simplified error handling but is now considered redundant. The package author can add a //go:fix inline comment above its definition. After updating the dependency, a developer runs go fix ./..., and all calls like mustBind(req, &data) are expanded into the underlying code (e.g., if err := json.NewDecoder(req.Body).Decode(&data); err != nil { ... }). The developer can then review and refine the code if needed.

Technical Insights: Correctness and Safety

Implementing a source‑level inliner that works for real‑world Go code is non‑trivial. The algorithm must handle:

  • Variable shadowing: Rename variables in the inlined body to avoid colliding with names in the caller’s scope.
  • Control flow: Preserve return, break, continue, and goto statements, which may need rewriting when moved into a different context.
  • Defer and recover: Inline functions that use defer require extra care because the deferred calls must execute in the correct order relative to the caller’s own deferred statements.

The Go team tested the inliner against a large corpus of open‑source Go code to ensure it produces correct results for every legal program. Errors trigger diagnostics that explain why the inliner cannot proceed, so developers never get silently broken code.

Conclusion

The combination of //go:fix inline and the source‑level inliner opens up exciting possibilities for automated code maintenance. Package authors can now write their own modernization rules without waiting for the Go team to add a dedicated go fix analyzer. Users benefit from automated updates that keep their codebases clean and idiomatic, reducing technical debt with every upgrade.

For more details, read the official Go blog post on the new go fix and experiment with the inliner in Go 1.26. The future of Go tooling is not just about compiler optimizations—it’s about empowering developers to evolve their code safely and efficiently.