How to reuse go fix tool code

547 views
Skip to first unread message

Anuj Agrawal

unread,
Mar 15, 2018, 6:20:47 AM3/15/18
to golang-nuts
Hi,

I am looking to create a gofix for one of my projects. I thought of
importing the code in
https://github.com/golang/go/tree/master/src/cmd/fix so that I could
simply write a plugin, build a binary and give it to people who import
my code.

However, I do not see any exported symbols in this code. That leaves
me with only one choice - that I copy the code and then write the
plugin on top of it. That does not sound like a very great idea as
compared to being able to import the package itself.

Is there any particular reason for not exporting any symbols in the gofix code?

Thanks,
Anuj Agrawal

peterGo

unread,
Mar 16, 2018, 9:51:58 AM3/16/18
to golang-nuts
Anuj Agrawal,

Exporting an API carries a commitment to maintain and support that API. go fix was a special-purpose command, that was useful before the Go1 compatibility guarantee. I can see no reason to export a go fix API.
Peter

thepud...@gmail.com

unread,
Mar 18, 2018, 12:47:11 PM3/18/18
to golang-nuts
Hi Anuj,

Two quick comments.

1. FYI, in case you didn't see this in the recent vgo dependency management  discussion, go fix might start to be used much more broadly in the not-too-distant future across the go community as a way to help automate in some cases the ability to update client code as a way to help manage API changes, somewhat similar to how go fix was used by the core go team before go 1.0. See the snippet pasted below from one of rsc's recent blog posts, or visit that link for a longer discussion. Not yet implemented, but could be very nice if it happens.

2. I'm mildly curious what type of changes you are trying to do, including what aspect of your intended changes makes it such that the simpler 'gofmt -r' automated refactoring or the more flexible 'eg' example-based refactoring tool (from the go team) aren't applicable.  The 'eg' help is mildly annoying to track down via godoc, so forgive the volume of text but I pasted in the main help text below from 'eg' at the bottom of this post. 'eg' is focused on expression-level changes, so perhaps that is one reason it might not be applicable for you, but as I said I'm a bit curious about the specifics of your case...

========================================================
========================================================
Before Go 1, we relied heavily on go fix, which users ran after updating to a new Go release and finding their programs no longer compiled.

...

The ability to name and work with multiple incompatible versions of a package in a single program suggests a possible solution: if a v1 API function can be implemented as a wrapper around the v2 API, the wrapper implementation can double as the fix specification. For example, suppose v1 of an API has functions EnableFoo and DisableFoo and v2 replaces the pair with a single SetFoo(enabled bool). After v2 is released, v1 can be implemented as a wrapper around v2:

package p // v1

import v2 "p/v2"

func EnableFoo() {
//go:fix
v2.SetFoo(true)
}

func DisableFoo() {
//go:fix
v2.SetFoo(false)
}

The special //go:fix comments would indicate to go fix that the wrapper body that follows should be inlined into the call site. Then running go fix would rewrite calls to v1 EnableFoo to v2 SetFoo(true). The rewrite is easily specified and type-checked, since it is plain Go code. 
========================================================


========================================================
========================================================
This tool implements example-based refactoring of expressions.

The transformation is specified as a Go file defining two functions,
'before' and 'after', of identical types.  Each function body consists
of a single statement: either a return statement with a single
(possibly multi-valued) expression, or an expression statement.  The
'before' expression specifies a pattern and the 'after' expression its
replacement.

package P
  import ( "errors"; "fmt" )
  func before(s string) error { return fmt.Errorf("%s", s) }
  func after(s string)  error { return errors.New(s) }

The expression statement form is useful when the expression has no
result, for example:

  func before(msg string) { log.Fatalf("%s", msg) }
  func after(msg string)  { log.Fatal(msg) }

The parameters of both functions are wildcards that may match any
expression assignable to that type.  If the pattern contains multiple
occurrences of the same parameter, each must match the same expression
in the input for the pattern to match.  If the replacement contains
multiple occurrences of the same parameter, the expression will be
duplicated, possibly changing the side-effects.

The tool analyses all Go code in the packages specified by the
arguments, replacing all occurrences of the pattern with the
substitution.

So, the transform above would change this input:
err := fmt.Errorf("%s", "error: " + msg)
to this output:
err := errors.New("error: " + msg)

Identifiers, including qualified identifiers (p.X) are considered to
match only if they denote the same object.  This allows correct
matching even in the presence of dot imports, named imports and
locally shadowed package names in the input program.

Matching of type syntax is semantic, not syntactic: type syntax in the
pattern matches type syntax in the input if the types are identical.
Thus, func(x int) matches func(y int).

This tool was inspired by other example-based refactoring tools,
'gofmt -r' for Go and Refaster for Java.


LIMITATIONS
===========

EXPRESSIVENESS

Only refactorings that replace one expression with another, regardless
of the expression's context, may be expressed.  Refactoring arbitrary
statements (or sequences of statements) is a less well-defined problem
and is less amenable to this approach.

A pattern that contains a function literal (and hence statements)
never matches.

There is no way to generalize over related types, e.g. to express that
a wildcard may have any integer type, for example.

It is not possible to replace an expression by one of a different
type, even in contexts where this is legal, such as x in fmt.Print(x).

The struct literals T{x} and T{K: x} cannot both be matched by a single
template.


SAFETY

Verifying that a transformation does not introduce type errors is very
complex in the general case.  An innocuous-looking replacement of one
constant by another (e.g. 1 to 2) may cause type errors relating to
array types and indices, for example.  The tool performs only very
superficial checks of type preservation.


IMPORTS

Although the matching algorithm is fully aware of scoping rules, the
replacement algorithm is not, so the replacement code may contain
incorrect identifier syntax for imported objects if there are dot
imports, named imports or locally shadowed package names in the input
program.

Imports are added as needed, but they are not removed as needed.
Run 'goimports' on the modified file for now.

Dot imports are forbidden in the template.


TIPS
====

Sometimes a little creativity is required to implement the desired
migration.  This section lists a few tips and tricks.

To remove the final parameter from a function, temporarily change the
function signature so that the final parameter is variadic, as this
allows legal calls both with and without the argument.  Then use eg to
remove the final argument from all callers, and remove the variadic
parameter by hand.  The reverse process can be used to add a final
parameter.

To add or remove parameters other than the final one, you must do it in
stages: (1) declare a variant function f' with a different name and the
desired parameters; (2) use eg to transform calls to f into calls to f',
changing the arguments as needed; (3) change the declaration of f to
match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
========================================================

--thepudds

David Collier-Brown

unread,
Mar 18, 2018, 7:49:55 PM3/18/18
to golang-nuts
A minor side comment: 


On Sunday, March 18, 2018 at 12:47:11 PM UTC-4, thepud...@gmail.com wrote:
Only refactorings that replace one expression with another, regardless
of the expression's context, may be expressed.  Refactoring arbitrary
statements (or sequences of statements) is a less well-defined problem
and is less amenable to this approach.

The latter used to be my life, and we adressed it with tools to make applying human judgement easy. Eg,  "port -- a program to strength-reduce the task of porting from defusing live bombs to something much more like fixing compilation errors", at https://github.com/davecb/port

The same can be applied to Go, as you'd guess, but is a much different problem than we have here.

Anuj Agrawal

unread,
Aug 5, 2018, 12:43:38 PM8/5/18
to thepud...@gmail.com, golang-nuts
On Sun, Mar 18, 2018 at 10:17 PM, <thepud...@gmail.com> wrote:
Hi Anuj,

Two quick comments.

1. FYI, in case you didn't see this in the recent vgo dependency management  discussion, go fix might start to be used much more broadly in the not-too-distant future across the go community as a way to help automate in some cases the ability to update client code as a way to help manage API changes, somewhat similar to how go fix was used by the core go team before go 1.0. See the snippet pasted below from one of rsc's recent blog posts, or visit that link for a longer discussion. Not yet implemented, but could be very nice if it happens.

2. I'm mildly curious what type of changes you are trying to do, including what aspect of your intended changes makes it such that the simpler 'gofmt -r' automated refactoring or the more flexible 'eg' example-based refactoring tool (from the go team) aren't applicable.  The 'eg' help is mildly annoying to track down via godoc, so forgive the volume of text but I pasted in the main help text below from 'eg' at the bottom of this post. 'eg' is focused on expression-level changes, so perhaps that is one reason it might not be applicable for you, but as I said I'm a bit curious about the specifics of your case...


Thanks, I will try it out and will post here if I find a case where it does not work. I had read the gofix blog and thought it should solve my problem.
 

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages