Skip to content

x/tools/go/analysis/passes/inline: unclear how to use //go:fix inline to phase out the use of promoted methods #75875

@mvdan

Description

@mvdan

Suppose I have a public package API like:

package pkg

type Foo struct {
	embedded
	someOtherField int
}

type embedded struct {
	Desc string
}

// This method is deprecated and it works, but it does not offer a `//go:fix inline`.
//
// Deprecated: use [Description].
func (e embedded) Description() string {
	return e.Desc
}

func Description(f Foo) string {
	return f.Desc
}

I want all users of embedded.Description to move to Description. For example:

package main

import "domain.test/pkg"

func main() {
	f := pkg.Foo{}
	f.Desc = "foo value"
	// Before:
	println("method:", f.Description())
	// After:
	println("global func:", pkg.Description(f))
}

I don't want to have to do that by hand, nor do I want my users to have to do it by hand, and given that the rewrite is fairly simple, I'd like to leverage //go:fix inline.

The naive attempt is to rewrite embedded.Description in terms of the global func, much like the before->after transformation in func main:

// This method is deprecated and tries to offer a `//go:fix inline`, but it doesn't work at all.
//
// Deprecated: use [Description].
//
//go:fix inline
func (e embedded) Description() string {
	return Description(e) // typecheck failure; Description expects type "Foo"
}

In other words, I cannot use //go:fix inline to represent a rewrite as follows:

For each call of the promoted method `Foo.Description` like `f.Description()`, swap to the global func `Description` like `Description(f)`.

I don't actually care about calls to the non-promoted embedded.Description method; it's an unexported type, and the method is not called directly in the same package.

It's tempting to say that I should manually promote the method and then use //go:fix inline, i.e. removing embedded.Description and adding:

// This method is deprecated and offers a `//go:fix inline`.
//
// Deprecated: use [Description].
//
//go:fix inline
func (f Foo) Description() string {
	return Description(f)
}

However, there are more than a dozen types embedding the embedded type, so this would be pretty repetitive. And it's a form of API change; for example, it could break code like:

type OverrideDesc struct{}

func (o OverrideDesc) Description() string { return "override" }

type Bar struct {
    OverrideDesc
    pkg.Foo
}

With the original code, Bar.Description is a valid method, promoted from OverrideDesc. With the manual promotion to Foo.Description, then Bar is embedding two types with a method Description at the same depth, so neither wins, and Bar.Description is not a valid method.

I do not know that there's a good solution here, or that we should do anything here. Perhaps we think that such embeddings and method promotions are rare enough to not bother with. However, I should note that I'm doing this deprecation and attempted refactoring precisely to unwind old promoted methods.

cc @adonovan

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.ToolsThis label describes issues relating to any tools in the x/tools repository.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions