-
Notifications
You must be signed in to change notification settings - Fork 18.7k
Description
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