-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/fix: automate migrations for simple deprecations #32816
Comments
How would this deal with Go versions? For example, Perhaps it can grab the oldest supported Go version from the optional line in |
@mvdan, good question. I'd say that it should not attempt to do anything special with Go versions. For stuff in the standard library in particular, perhaps that implies that we shouldn't add |
I personally think this is a losing battle. Certain libraries support older Go versions, for their own reasons. You can say "just don't use An alternative is to use "two Go releases ago" as the fallback version, instead of 1.0. Then a module can specify an older or newer version as needed. |
The "Automatic API Updates" section of the initial vgo blog series sketched out an alternative. It might be interesting to contrast this proposal with that earlier sketch. One difference between that earlier sketch and the proposal here is that the earlier sketch I think had fewer comment variations, but I think relied on the v1 code being implemented as a shim around v2 (and perhaps it could in theory also support the reverse if v2 is implemented as a wrapper around v1). Example snippet from that older sketch:
In contrast, this newer proposal also addresses deprecation use cases (without a need for a major version change). |
@mvdan, I suppose we could use the annotations in |
@thepudds, note that @rsc's sketch there is substantially more powerful than what I'm proposing here. In particular, I'm not assuming that One of the examples there is:
but under this proposal that would be expressed as:
Of course, once |
Ah, noted. |
A few questions:
Trying to get a scope of what you're proposing.
|
I would generally expect so, yes: However, I think we still want both comments in general.
Sure: you could envision using it for package url
type URL struct {
[…]
// encoded path hint
RawPath string //go:fix-to .EscapedPath()
[…]
}
Agreed. It's the most magical, but also arguably the most useful. 🙂 I think it will require particular care around how we map
Correct. I could envision some ways to use example-based refactoring to convert uses of the
Sadly, no: I could imagine a package net
type Dialer {
[…]
DualStack bool //go:fix-write-to .setDualStack(_)
[…]
}
//go:forward
func (d *Dialer) setDualStack(dualStack bool) {
if !dualStack {
d.FallbackDelay = -1
}
}
Yes: package net
//go:forward
func (d *Dialer) Dial(network, address string) (Conn, error) {
return d.DialContext(context.TODO(), network, address)
}
Yep. Note that it's easy to introduce |
Is go-fix intended for changes that aren't strictly backwards compatible? |
I think that's an open question. This proposal leaves that decision up to package authors: nothing stops them from applying a |
(And do note that a |
|
Maybe, but since Note that the proposed But that's an interesting idea, and I would not object to adding a variant of
That seems like a reasonable thing for |
In general I'm working on the assumption that folks don't go around monkey-patching variables in other packages. (For example, you should never swap out I don't have a solution to that in general, but there are complementary proposals (such as #21130) that could help to detect those sorts of errors. Note that you can also use the race detector to detect these sorts of modifications: var ZR = Rectangle{}
func init() {
go func() { _ = ZR }() // Data race if anybody mutates ZR.
}() |
I would like to see this simpified down to a single comment that means "this is 100% guaranteed semantically OK, because it is literally a definitional substitution", and because the tool is For the io constants, os can do
const SEEK_SET = int(io.SeekSet) Also the //go:fix comments should not be in the doc comment; they should be above them instead. You want to be able to tell people "it is always OK to run go fix". A suggestion mechanism is fine as part of Deprecated comments but we should consider that in a separate proposal and separate tool. |
Is there any worry about name squatting on |
@cep21, did you have anything else in mind for it?
Yes.
The intent of this proposal is specifically to mark that the mapping is 1:1, and furthermore that the mapping is straightforward enough that a human doesn't need to think about how to apply it. Without such an annotation, I don't think it's feasible to build a tool that can decide when a |
Can you give an example that fits with rcs's simplified proposal? He mentions
In those cases, I can't see how it wouldn't be expected and safe for a |
More concretely, if the proposal is limited to just type aliases (as an example), in which situation would I not want to fix this? // deprecated: blah blah
type Circuit = newpackage.Circuit |
Are there any surprising consequences of the following proposal:
|
@cep21, the interesting use-cases for |
Note that one of the examples — |
That's not allowed by rcs's simplification since it's not guaranteed to be backwards compatible: It's possible to reassign var types. I may be misunderstanding what "definitional substitution" and "suggestion mechanism" means. Can those terms be defined? Either way, it's not a big concern and happy this issue is getting noticed! |
@cep21, I've been thinking some more about the interaction with deprecation. From discussions with @ianthehat, it seems that he would prefer something similar. At the very least, I think a heuristic based solely on // Deprecated: use T2.
type T1 = T2
// Deprecated: use T3
type T2 = T3 The arguably “correct” behavior is to inline uses of |
The interaction with literal values is interesting, though. In the declaration // Deprecated: Use a literal image.Rectangle{} instead.
var ZR = Rectangle{} it would certainly appropriate to replace the value with a literal. However, sometimes a value with an existing // Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd.
const (
SEEK_SET int = 0 // seek relative to the origin of the file
SEEK_CUR int = 1 // seek relative to the current offset
SEEK_END int = 2 // seek relative to the end
) Moreover, the problem is even worse if the initializer is not idempotent: // Deprecated: use os.ErrTemporary directly.
var ErrFlaky = fmt.Errorf("%w: operation flaky", os.ErrTemporary) // Deprecated: use a caller-allocated slice.
var Buffer = make([]byte, 1<<10) The non-idempotent case, at least, seems easier to detect, although I'm not sure whether it's easy enough to be effective in general. |
Trivial: rename a local from fcon (forwardable const) to incon (inlinable const) to match terminology. For golang/go#32816. Change-Id: I7d61f055c7057c30b240c076b8710f47f2bf86d1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/648715 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
Change https://go.dev/cl/649055 mentions this issue: |
Change https://go.dev/cl/649057 mentions this issue: |
Change https://go.dev/cl/649056 mentions this issue: |
Change https://go.dev/cl/648976 mentions this issue: |
This CL adds //go:fix inline annotations to some deprecated functions that may be inlined. Updates golang/go#32816 Change-Id: I2e8e82bee054721f266506af24ea39cf2e8b7983 Reviewed-on: https://go-review.googlesource.com/c/tools/+/649056 LUCI-TryBot-Result: Go LUCI <[email protected]> Commit-Queue: Alan Donovan <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
This inlines calls to a number of deprecated functions in both std and x/tools. Updates golang/go#32816 Change-Id: Id7f89983b1428fd3c042947dbecf07349f0bc134 Reviewed-on: https://go-review.googlesource.com/c/tools/+/649057 LUCI-TryBot-Result: Go LUCI <[email protected]> Commit-Queue: Alan Donovan <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
This was achieved by annotiating them with //go:fix inline, running gopls/internal/analysis/gofix/main.go -fix ./... then deleting them. Update golang/go#32816 Change-Id: If65dbf8bfcad796ef274d80804daa135e8ccabf9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/648976 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Commit-Queue: Alan Donovan <[email protected]>
It would be nice to replace the variable How do we feel about extending |
I'm in favor. There are two cases:
We could address both of them, but I suspect the second is sufficiently rare (and tricky) that it is not worth bothering with, at least for now. |
Offer to replace inlinable type aliases whose RHS is a named type. Despite the amount of new code, there is little to test, because most of it duplicates constants. When there is a chain of inlinable type aliases, as in: type A = T type AA = A var v AA we don't follow the chain to the end. Instead, the first set of fixes produces: type A = T type AA = T var v A That is, the replacements happen "in parallel." A second round of fixes would rewrite var v A to var v T This case is rare enough that it's not worth doing better. For golang/go#32816. Change-Id: Ib6854d60b26a273b592a297cb9a650a31e094392 Reviewed-on: https://go-review.googlesource.com/c/tools/+/649055 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
Change https://go.dev/cl/649476 mentions this issue: |
Split the two passes into two separate functions. Declare a type to hold common state. No behavior changes. For golang/go#32816. Change-Id: I571956859b12687d824f36c80f75deb96db38d92 Reviewed-on: https://go-review.googlesource.com/c/tools/+/649476 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
Change https://go.dev/cl/649456 mentions this issue: |
Refactor so that each kind of inlinable (function, type alias, constant) has its own function for each pass. Refactoring only; no behavior changes. For golang/go#32816. Change-Id: I2f09b4020bcf03409664cee3b8379417d8717fa6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/649456 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
In particular, we apply it only to functions where it is always a code improvement to inline the call. We also apply it to some constants. In a few cases this may introduce a panic statement at the caller, which is debatable, but making the potential for panic evident is the purpose of the deprecation. The gofix analyzer in gopls v0.18 will show a diagnostic for calls to the annotated functions, and will offer to inline the call. The new //go:fix annotation needs a special exemption in the pragma check in the compiler. Updates #32816 Change-Id: I43bf15648ac12251734109eb7102394f8a76d55e Reviewed-on: https://go-review.googlesource.com/c/go/+/648995 Reviewed-by: Dmitri Shuralyov <[email protected]> Commit-Queue: Alan Donovan <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
Change https://go.dev/cl/650755 mentions this issue: |
@adonovan, I take the opposite view: if we inline vars at all, we should only inline those that have both Back in 2019 @rsc wrote "[//go:fix inline should mean] "this is 100% guaranteed semantically OK, because it is literally a definitional substitution." Nobody can make that guarantee for an exported variable without a Thoughts, @rsc @aclements? |
Much of the early discussion on this issue talked about modifying |
I'm pretty squeamish about anything that encourages people to use My inclination is that we just shouldn't do this for variables. Go does not have a mechanism for forwarding variables, therefore |
Change https://go.dev/cl/651615 mentions this issue: |
Support inlining an alias with an arbitrary right-hand side. The type checker gives us almost everything we need to inline an alias; the only thing missing is the bit that says that a //go:fix directive was present. So the fact is an empty struct. Skip aliases that mention arrays. The array length expression isn't represented, and it may refer to other values, so inlining it would incorrectly decouple the inlined expression from the original. For golang/go#32816. Change-Id: I2e5ff1bd69a0f88cd7cb396dee8d4b426988d1cc Reviewed-on: https://go-review.googlesource.com/c/tools/+/650755 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
For golang/go#32816. Change-Id: Icf805984f812af19c720d4f477ed12a97a5dd68d Reviewed-on: https://go-review.googlesource.com/c/tools/+/651615 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
Change https://go.dev/cl/651617 mentions this issue: |
Change https://go.dev/cl/651655 mentions this issue: |
I don't understand why Go support for forwarding variables needs to be a prerequisite for this kind of refactoring. It would be great if we could restrict global variables in a way that makes the inline variable refactoring 100% safe to refactor, but I disagree that it's necessary. I think that some amount of risk is acceptable for automated refactorings because we have unit tests and human reviewers to catch mistakes. |
Change https://go.dev/cl/653436 mentions this issue: |
The tooling will not yet offer to inline calls to these functions because it doesn't handle generics (golang/go#68236), but it will soon. Updates golang/go#32816 Change-Id: Ic19940efddab6267ca92f670ddc2f8029bc93402 Reviewed-on: https://go-review.googlesource.com/c/exp/+/653436 Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
Support inlining generic aliases. For golang/go#32816. Change-Id: Ic65e6fb30d65ee0f7d6e0093fd882a675de71da4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/651617 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
An array type can be inlined if its length is a literal integer. For golang/go#32816. Change-Id: I80c7f18721c813a0ea7039411ddf8a804b5bf0b5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/651655 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
(I've mentioned this in passing on #32014 and #27248, but I don't see an actual proposal filed yet — so here it is!)
Over time, the author of a package may notice certain patterns in the usage of their API that they want to make simpler or clearer for users. When that happens, they may suggest that users update their own code to use the new API.
In many cases, the process of updating is nearly trivial, so it would be nice if some tool in the standard distribution could apply those trivial updates mechanically. The
go fix
subcommand was used for similar updates to the language itself, so perhaps it could be adapted to work for at least the simple cases.The very simplest sort of update rewrites a call to some function or use of some variable or constant to instead call some other function or use some other variable, constant, or expression.
To draw some examples from the standard library:
archive/tar.TypeRegA
says to useTypeReg
instead.archive/zip.FileHeader.{Compressed,Uncompressed}Size
say to use the corresponding*64
fields instead.image.ZR
andimage.ZP
say to use zero struct literals instead.go/importer.For
says to useForCompiler
, and is implemented as a trivial call toForCompiler
with an additional argument.io.SEEK_*
constants says to use correspondingio.Seek*
constants instead.syscall.StringByte{Slice,Ptr}
say to use the correspondingByte*FromString
functions, and are implemented as short wrappers around those functions.Outside the standard library, this pattern can also occur when a package makes a breaking change: if the
v3
API can express all of the same capabilities asv2
, thenv2
can be implemented as a wrapper aroundv3
— and references to thev2
identifiers can be replaced with direct references to the underlyingv3
identifiers.See the current proposal details in #32816 (comment) below.
Previous draft (prior to #32816 (comment)):
I propose that we establish a consistent convention to mark these kinds of mechanical replacements, and improve
cmd/fix
to be able to apply them.I'm not particularly tied to any given convention, but in the spirit of having something concrete to discuss, I propose two tokens,
//go:fix-to
and//go:forward
, as follows://go:fix-to EXPR
on a constant or variable indicates thatgo fix
should replace occurrences of that constant or variable with the expressionEXPR
. Free variables in the expression are interpreted as package names, andgo fix
may rewrite them to avoid collisions with non-packages.//go:fix-to .EXPR
on struct field indicates thatgo fix
should replace references to that field with the given selector expression (which may be a field access OR a method call) on the same value. Free variables are again interpreted as package names. If the expression is a method call, it replaces only reads of the field.//go:forward
on an individual constant or variable indicates thatgo fix
should replace the constant or variable with the expression from which it is initialized. If a constant is declared with a type but initialized from an untyped expression, the replacement is wrapped in a conversion to that type.//go:forward
on aconst
orvar
block indicates that every identifier within the block should be replaced by the expression from which it is initialized.//go:forward
on a method or function indicates thatgo fix
should inline its body at the call site, renaming any local variables and labels as needed to avoid collisions. The function or method must have at most onereturn
statement and nodefer
statements.//go:forward
on a type alias indicates thatgo fix
should replace references to the alias name with the (immediate) type it aliases.All of these rewrites must be acyclic: the result of a (transitively applied) rewrite must not refer to the thing being rewritten.
Note that there is a prototype example-based refactoring tool for Go in
golang.org/x/tools/cmd/eg
that supported a broader set of refactoring rules. It might be useful as a starting point.For the above examples, the resulting declarations might look like:
This proposal does not address the
archive/tar
use-case: callers producing archives might be able to transparently migrate fromTypeRegA
toTypeReg
, but it isn't obvious that callers consuming archives can safely do so.Nor does this proposal does not address the
archive/zip.FileHeader
use-case: the deprecation there indicates a loss of precision, and the caller should probably be prompted to make related fixes in the surrounding code.Both of those cases might be addressable with a deeper example-based refactoring tool, but (I claim) are too subtle to handle directly in
go fix
.CC @marwan-at-work @thepudds @jayconrod @ianthehat @myitcv @cep21
The text was updated successfully, but these errors were encountered: