summaryrefslogtreecommitdiffstats
path: root/src/cmd/fix
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
commitf6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch)
tree7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/fix
parentInitial commit. (diff)
downloadgolang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz
golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/fix')
-rw-r--r--src/cmd/fix/buildtag.go51
-rw-r--r--src/cmd/fix/buildtag_test.go34
-rw-r--r--src/cmd/fix/cftype.go147
-rw-r--r--src/cmd/fix/cftype_test.go241
-rw-r--r--src/cmd/fix/context.go25
-rw-r--r--src/cmd/fix/context_test.go42
-rw-r--r--src/cmd/fix/doc.go37
-rw-r--r--src/cmd/fix/egltype.go60
-rw-r--r--src/cmd/fix/egltype_test.go214
-rw-r--r--src/cmd/fix/fix.go566
-rw-r--r--src/cmd/fix/gotypes.go75
-rw-r--r--src/cmd/fix/gotypes_test.go89
-rw-r--r--src/cmd/fix/import_test.go458
-rw-r--r--src/cmd/fix/jnitype.go69
-rw-r--r--src/cmd/fix/jnitype_test.go203
-rw-r--r--src/cmd/fix/main.go273
-rw-r--r--src/cmd/fix/main_test.go166
-rw-r--r--src/cmd/fix/netipv6zone.go68
-rw-r--r--src/cmd/fix/netipv6zone_test.go43
-rw-r--r--src/cmd/fix/printerconfig.go61
-rw-r--r--src/cmd/fix/printerconfig_test.go37
-rw-r--r--src/cmd/fix/typecheck.go813
22 files changed, 3772 insertions, 0 deletions
diff --git a/src/cmd/fix/buildtag.go b/src/cmd/fix/buildtag.go
new file mode 100644
index 0000000..5f4fbfe
--- /dev/null
+++ b/src/cmd/fix/buildtag.go
@@ -0,0 +1,51 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+ "strings"
+)
+
+func init() {
+ register(buildtagFix)
+}
+
+const buildtagGoVersionCutoff = 1_18
+
+var buildtagFix = fix{
+ name: "buildtag",
+ date: "2021-08-25",
+ f: buildtag,
+ desc: `Remove +build comments from modules using Go 1.18 or later`,
+}
+
+func buildtag(f *ast.File) bool {
+ if goVersion < buildtagGoVersionCutoff {
+ return false
+ }
+
+ // File is already gofmt-ed, so we know that if there are +build lines,
+ // they are in a comment group that starts with a //go:build line followed
+ // by a blank line. While we cannot delete comments from an AST and
+ // expect consistent output in general, this specific case - deleting only
+ // some lines from a comment block - does format correctly.
+ fixed := false
+ for _, g := range f.Comments {
+ sawGoBuild := false
+ for i, c := range g.List {
+ if strings.HasPrefix(c.Text, "//go:build ") {
+ sawGoBuild = true
+ }
+ if sawGoBuild && strings.HasPrefix(c.Text, "// +build ") {
+ g.List = g.List[:i]
+ fixed = true
+ break
+ }
+ }
+ }
+
+ return fixed
+}
diff --git a/src/cmd/fix/buildtag_test.go b/src/cmd/fix/buildtag_test.go
new file mode 100644
index 0000000..1c6efbe
--- /dev/null
+++ b/src/cmd/fix/buildtag_test.go
@@ -0,0 +1,34 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(buildtagTests, buildtag)
+}
+
+var buildtagTests = []testCase{
+ {
+ Name: "buildtag.oldGo",
+ Version: 1_10,
+ In: `//go:build yes
+// +build yes
+
+package main
+`,
+ },
+ {
+ Name: "buildtag.new",
+ Version: 1_99,
+ In: `//go:build yes
+// +build yes
+
+package main
+`,
+ Out: `//go:build yes
+
+package main
+`,
+ },
+}
diff --git a/src/cmd/fix/cftype.go b/src/cmd/fix/cftype.go
new file mode 100644
index 0000000..d4fcc44
--- /dev/null
+++ b/src/cmd/fix/cftype.go
@@ -0,0 +1,147 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+ "go/token"
+ "reflect"
+ "strings"
+)
+
+func init() {
+ register(cftypeFix)
+}
+
+var cftypeFix = fix{
+ name: "cftype",
+ date: "2017-09-27",
+ f: cftypefix,
+ desc: `Fixes initializers and casts of C.*Ref and JNI types`,
+ disabled: false,
+}
+
+// Old state:
+//
+// type CFTypeRef unsafe.Pointer
+//
+// New state:
+//
+// type CFTypeRef uintptr
+//
+// and similar for other *Ref types.
+// This fix finds nils initializing these types and replaces the nils with 0s.
+func cftypefix(f *ast.File) bool {
+ return typefix(f, func(s string) bool {
+ return strings.HasPrefix(s, "C.") && strings.HasSuffix(s, "Ref") && s != "C.CFAllocatorRef"
+ })
+}
+
+// typefix replaces nil with 0 for all nils whose type, when passed to badType, returns true.
+func typefix(f *ast.File, badType func(string) bool) bool {
+ if !imports(f, "C") {
+ return false
+ }
+ typeof, _ := typecheck(&TypeConfig{}, f)
+ changed := false
+
+ // step 1: Find all the nils with the offending types.
+ // Compute their replacement.
+ badNils := map[any]ast.Expr{}
+ walk(f, func(n any) {
+ if i, ok := n.(*ast.Ident); ok && i.Name == "nil" && badType(typeof[n]) {
+ badNils[n] = &ast.BasicLit{ValuePos: i.NamePos, Kind: token.INT, Value: "0"}
+ }
+ })
+
+ // step 2: find all uses of the bad nils, replace them with 0.
+ // There's no easy way to map from an ast.Expr to all the places that use them, so
+ // we use reflect to find all such references.
+ if len(badNils) > 0 {
+ exprType := reflect.TypeFor[ast.Expr]()
+ exprSliceType := reflect.TypeFor[[]ast.Expr]()
+ walk(f, func(n any) {
+ if n == nil {
+ return
+ }
+ v := reflect.ValueOf(n)
+ if v.Type().Kind() != reflect.Pointer {
+ return
+ }
+ if v.IsNil() {
+ return
+ }
+ v = v.Elem()
+ if v.Type().Kind() != reflect.Struct {
+ return
+ }
+ for i := 0; i < v.NumField(); i++ {
+ f := v.Field(i)
+ if f.Type() == exprType {
+ if r := badNils[f.Interface()]; r != nil {
+ f.Set(reflect.ValueOf(r))
+ changed = true
+ }
+ }
+ if f.Type() == exprSliceType {
+ for j := 0; j < f.Len(); j++ {
+ e := f.Index(j)
+ if r := badNils[e.Interface()]; r != nil {
+ e.Set(reflect.ValueOf(r))
+ changed = true
+ }
+ }
+ }
+ }
+ })
+ }
+
+ // step 3: fix up invalid casts.
+ // It used to be ok to cast between *unsafe.Pointer and *C.CFTypeRef in a single step.
+ // Now we need unsafe.Pointer as an intermediate cast.
+ // (*unsafe.Pointer)(x) where x is type *bad -> (*unsafe.Pointer)(unsafe.Pointer(x))
+ // (*bad.type)(x) where x is type *unsafe.Pointer -> (*bad.type)(unsafe.Pointer(x))
+ walk(f, func(n any) {
+ if n == nil {
+ return
+ }
+ // Find pattern like (*a.b)(x)
+ c, ok := n.(*ast.CallExpr)
+ if !ok {
+ return
+ }
+ if len(c.Args) != 1 {
+ return
+ }
+ p, ok := c.Fun.(*ast.ParenExpr)
+ if !ok {
+ return
+ }
+ s, ok := p.X.(*ast.StarExpr)
+ if !ok {
+ return
+ }
+ t, ok := s.X.(*ast.SelectorExpr)
+ if !ok {
+ return
+ }
+ pkg, ok := t.X.(*ast.Ident)
+ if !ok {
+ return
+ }
+ dst := pkg.Name + "." + t.Sel.Name
+ src := typeof[c.Args[0]]
+ if badType(dst) && src == "*unsafe.Pointer" ||
+ dst == "unsafe.Pointer" && strings.HasPrefix(src, "*") && badType(src[1:]) {
+ c.Args[0] = &ast.CallExpr{
+ Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "unsafe"}, Sel: &ast.Ident{Name: "Pointer"}},
+ Args: []ast.Expr{c.Args[0]},
+ }
+ changed = true
+ }
+ })
+
+ return changed
+}
diff --git a/src/cmd/fix/cftype_test.go b/src/cmd/fix/cftype_test.go
new file mode 100644
index 0000000..cde47f2
--- /dev/null
+++ b/src/cmd/fix/cftype_test.go
@@ -0,0 +1,241 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(cftypeTests, cftypefix)
+}
+
+var cftypeTests = []testCase{
+ {
+ Name: "cftype.localVariable",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+func f() {
+ var x C.CFTypeRef = nil
+ x = nil
+ x, x = nil, nil
+}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+func f() {
+ var x C.CFTypeRef = 0
+ x = 0
+ x, x = 0, 0
+}
+`,
+ },
+ {
+ Name: "cftype.globalVariable",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x C.CFTypeRef = nil
+
+func f() {
+ x = nil
+}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x C.CFTypeRef = 0
+
+func f() {
+ x = 0
+}
+`,
+ },
+ {
+ Name: "cftype.EqualArgument",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x C.CFTypeRef
+var y = x == nil
+var z = x != nil
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x C.CFTypeRef
+var y = x == 0
+var z = x != 0
+`,
+ },
+ {
+ Name: "cftype.StructField",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+type T struct {
+ x C.CFTypeRef
+}
+
+var t = T{x: nil}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+type T struct {
+ x C.CFTypeRef
+}
+
+var t = T{x: 0}
+`,
+ },
+ {
+ Name: "cftype.FunctionArgument",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+func f(x C.CFTypeRef) {
+}
+
+func g() {
+ f(nil)
+}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+func f(x C.CFTypeRef) {
+}
+
+func g() {
+ f(0)
+}
+`,
+ },
+ {
+ Name: "cftype.ArrayElement",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = [3]C.CFTypeRef{nil, nil, nil}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = [3]C.CFTypeRef{0, 0, 0}
+`,
+ },
+ {
+ Name: "cftype.SliceElement",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = []C.CFTypeRef{nil, nil, nil}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = []C.CFTypeRef{0, 0, 0}
+`,
+ },
+ {
+ Name: "cftype.MapKey",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = map[C.CFTypeRef]int{nil: 0}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = map[C.CFTypeRef]int{0: 0}
+`,
+ },
+ {
+ Name: "cftype.MapValue",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = map[int]C.CFTypeRef{0: nil}
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x = map[int]C.CFTypeRef{0: 0}
+`,
+ },
+ {
+ Name: "cftype.Conversion1",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x C.CFTypeRef
+var y = (*unsafe.Pointer)(&x)
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x C.CFTypeRef
+var y = (*unsafe.Pointer)(unsafe.Pointer(&x))
+`,
+ },
+ {
+ Name: "cftype.Conversion2",
+ In: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x unsafe.Pointer
+var y = (*C.CFTypeRef)(&x)
+`,
+ Out: `package main
+
+// typedef const void *CFTypeRef;
+import "C"
+
+var x unsafe.Pointer
+var y = (*C.CFTypeRef)(unsafe.Pointer(&x))
+`,
+ },
+}
diff --git a/src/cmd/fix/context.go b/src/cmd/fix/context.go
new file mode 100644
index 0000000..1107f4d
--- /dev/null
+++ b/src/cmd/fix/context.go
@@ -0,0 +1,25 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+)
+
+func init() {
+ register(contextFix)
+}
+
+var contextFix = fix{
+ name: "context",
+ date: "2016-09-09",
+ f: ctxfix,
+ desc: `Change imports of golang.org/x/net/context to context`,
+ disabled: false,
+}
+
+func ctxfix(f *ast.File) bool {
+ return rewriteImport(f, "golang.org/x/net/context", "context")
+}
diff --git a/src/cmd/fix/context_test.go b/src/cmd/fix/context_test.go
new file mode 100644
index 0000000..935d0d7
--- /dev/null
+++ b/src/cmd/fix/context_test.go
@@ -0,0 +1,42 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(contextTests, ctxfix)
+}
+
+var contextTests = []testCase{
+ {
+ Name: "context.0",
+ In: `package main
+
+import "golang.org/x/net/context"
+
+var _ = "golang.org/x/net/context"
+`,
+ Out: `package main
+
+import "context"
+
+var _ = "golang.org/x/net/context"
+`,
+ },
+ {
+ Name: "context.1",
+ In: `package main
+
+import ctx "golang.org/x/net/context"
+
+var _ = ctx.Background()
+`,
+ Out: `package main
+
+import ctx "context"
+
+var _ = ctx.Background()
+`,
+ },
+}
diff --git a/src/cmd/fix/doc.go b/src/cmd/fix/doc.go
new file mode 100644
index 0000000..062eb79
--- /dev/null
+++ b/src/cmd/fix/doc.go
@@ -0,0 +1,37 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Fix finds Go programs that use old APIs and rewrites them to use
+newer ones. After you update to a new Go release, fix helps make
+the necessary changes to your programs.
+
+Usage:
+
+ go tool fix [-r name,...] [path ...]
+
+Without an explicit path, fix reads standard input and writes the
+result to standard output.
+
+If the named path is a file, fix rewrites the named files in place.
+If the named path is a directory, fix rewrites all .go files in that
+directory tree. When fix rewrites a file, it prints a line to standard
+error giving the name of the file and the rewrite applied.
+
+If the -diff flag is set, no files are rewritten. Instead fix prints
+the differences a rewrite would introduce.
+
+The -r flag restricts the set of rewrites considered to those in the
+named list. By default fix considers all known rewrites. Fix's
+rewrites are idempotent, so that it is safe to apply fix to updated
+or partially updated code even without using the -r flag.
+
+Fix prints the full list of fixes it can apply in its help output;
+to see them, run go tool fix -help.
+
+Fix does not make backup copies of the files that it edits.
+Instead, use a version control system's “diff” functionality to inspect
+the changes that fix makes before committing them.
+*/
+package main
diff --git a/src/cmd/fix/egltype.go b/src/cmd/fix/egltype.go
new file mode 100644
index 0000000..a096db6
--- /dev/null
+++ b/src/cmd/fix/egltype.go
@@ -0,0 +1,60 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+)
+
+func init() {
+ register(eglFixDisplay)
+ register(eglFixConfig)
+}
+
+var eglFixDisplay = fix{
+ name: "egl",
+ date: "2018-12-15",
+ f: eglfixDisp,
+ desc: `Fixes initializers of EGLDisplay`,
+ disabled: false,
+}
+
+// Old state:
+//
+// type EGLDisplay unsafe.Pointer
+//
+// New state:
+//
+// type EGLDisplay uintptr
+//
+// This fix finds nils initializing these types and replaces the nils with 0s.
+func eglfixDisp(f *ast.File) bool {
+ return typefix(f, func(s string) bool {
+ return s == "C.EGLDisplay"
+ })
+}
+
+var eglFixConfig = fix{
+ name: "eglconf",
+ date: "2020-05-30",
+ f: eglfixConfig,
+ desc: `Fixes initializers of EGLConfig`,
+ disabled: false,
+}
+
+// Old state:
+//
+// type EGLConfig unsafe.Pointer
+//
+// New state:
+//
+// type EGLConfig uintptr
+//
+// This fix finds nils initializing these types and replaces the nils with 0s.
+func eglfixConfig(f *ast.File) bool {
+ return typefix(f, func(s string) bool {
+ return s == "C.EGLConfig"
+ })
+}
diff --git a/src/cmd/fix/egltype_test.go b/src/cmd/fix/egltype_test.go
new file mode 100644
index 0000000..c44525c
--- /dev/null
+++ b/src/cmd/fix/egltype_test.go
@@ -0,0 +1,214 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "strings"
+
+func init() {
+ addTestCases(eglTestsFor("EGLDisplay"), eglfixDisp)
+ addTestCases(eglTestsFor("EGLConfig"), eglfixConfig)
+}
+
+func eglTestsFor(tname string) []testCase {
+ var eglTests = []testCase{
+ {
+ Name: "egl.localVariable",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+func f() {
+ var x C.$EGLTYPE = nil
+ x = nil
+ x, x = nil, nil
+}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+func f() {
+ var x C.$EGLTYPE = 0
+ x = 0
+ x, x = 0, 0
+}
+`,
+ },
+ {
+ Name: "egl.globalVariable",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x C.$EGLTYPE = nil
+
+func f() {
+ x = nil
+}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x C.$EGLTYPE = 0
+
+func f() {
+ x = 0
+}
+`,
+ },
+ {
+ Name: "egl.EqualArgument",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x C.$EGLTYPE
+var y = x == nil
+var z = x != nil
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x C.$EGLTYPE
+var y = x == 0
+var z = x != 0
+`,
+ },
+ {
+ Name: "egl.StructField",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+type T struct {
+ x C.$EGLTYPE
+}
+
+var t = T{x: nil}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+type T struct {
+ x C.$EGLTYPE
+}
+
+var t = T{x: 0}
+`,
+ },
+ {
+ Name: "egl.FunctionArgument",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+func f(x C.$EGLTYPE) {
+}
+
+func g() {
+ f(nil)
+}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+func f(x C.$EGLTYPE) {
+}
+
+func g() {
+ f(0)
+}
+`,
+ },
+ {
+ Name: "egl.ArrayElement",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = [3]C.$EGLTYPE{nil, nil, nil}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = [3]C.$EGLTYPE{0, 0, 0}
+`,
+ },
+ {
+ Name: "egl.SliceElement",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = []C.$EGLTYPE{nil, nil, nil}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = []C.$EGLTYPE{0, 0, 0}
+`,
+ },
+ {
+ Name: "egl.MapKey",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = map[C.$EGLTYPE]int{nil: 0}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = map[C.$EGLTYPE]int{0: 0}
+`,
+ },
+ {
+ Name: "egl.MapValue",
+ In: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = map[int]C.$EGLTYPE{0: nil}
+`,
+ Out: `package main
+
+// typedef void *$EGLTYPE;
+import "C"
+
+var x = map[int]C.$EGLTYPE{0: 0}
+`,
+ },
+ }
+ for i := range eglTests {
+ t := &eglTests[i]
+ t.In = strings.ReplaceAll(t.In, "$EGLTYPE", tname)
+ t.Out = strings.ReplaceAll(t.Out, "$EGLTYPE", tname)
+ }
+ return eglTests
+}
diff --git a/src/cmd/fix/fix.go b/src/cmd/fix/fix.go
new file mode 100644
index 0000000..7abdab2
--- /dev/null
+++ b/src/cmd/fix/fix.go
@@ -0,0 +1,566 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "path"
+ "strconv"
+)
+
+type fix struct {
+ name string
+ date string // date that fix was introduced, in YYYY-MM-DD format
+ f func(*ast.File) bool
+ desc string
+ disabled bool // whether this fix should be disabled by default
+}
+
+// main runs sort.Sort(byName(fixes)) before printing list of fixes.
+type byName []fix
+
+func (f byName) Len() int { return len(f) }
+func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
+func (f byName) Less(i, j int) bool { return f[i].name < f[j].name }
+
+// main runs sort.Sort(byDate(fixes)) before applying fixes.
+type byDate []fix
+
+func (f byDate) Len() int { return len(f) }
+func (f byDate) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
+func (f byDate) Less(i, j int) bool { return f[i].date < f[j].date }
+
+var fixes []fix
+
+func register(f fix) {
+ fixes = append(fixes, f)
+}
+
+// walk traverses the AST x, calling visit(y) for each node y in the tree but
+// also with a pointer to each ast.Expr, ast.Stmt, and *ast.BlockStmt,
+// in a bottom-up traversal.
+func walk(x any, visit func(any)) {
+ walkBeforeAfter(x, nop, visit)
+}
+
+func nop(any) {}
+
+// walkBeforeAfter is like walk but calls before(x) before traversing
+// x's children and after(x) afterward.
+func walkBeforeAfter(x any, before, after func(any)) {
+ before(x)
+
+ switch n := x.(type) {
+ default:
+ panic(fmt.Errorf("unexpected type %T in walkBeforeAfter", x))
+
+ case nil:
+
+ // pointers to interfaces
+ case *ast.Decl:
+ walkBeforeAfter(*n, before, after)
+ case *ast.Expr:
+ walkBeforeAfter(*n, before, after)
+ case *ast.Spec:
+ walkBeforeAfter(*n, before, after)
+ case *ast.Stmt:
+ walkBeforeAfter(*n, before, after)
+
+ // pointers to struct pointers
+ case **ast.BlockStmt:
+ walkBeforeAfter(*n, before, after)
+ case **ast.CallExpr:
+ walkBeforeAfter(*n, before, after)
+ case **ast.FieldList:
+ walkBeforeAfter(*n, before, after)
+ case **ast.FuncType:
+ walkBeforeAfter(*n, before, after)
+ case **ast.Ident:
+ walkBeforeAfter(*n, before, after)
+ case **ast.BasicLit:
+ walkBeforeAfter(*n, before, after)
+
+ // pointers to slices
+ case *[]ast.Decl:
+ walkBeforeAfter(*n, before, after)
+ case *[]ast.Expr:
+ walkBeforeAfter(*n, before, after)
+ case *[]*ast.File:
+ walkBeforeAfter(*n, before, after)
+ case *[]*ast.Ident:
+ walkBeforeAfter(*n, before, after)
+ case *[]ast.Spec:
+ walkBeforeAfter(*n, before, after)
+ case *[]ast.Stmt:
+ walkBeforeAfter(*n, before, after)
+
+ // These are ordered and grouped to match ../../go/ast/ast.go
+ case *ast.Field:
+ walkBeforeAfter(&n.Names, before, after)
+ walkBeforeAfter(&n.Type, before, after)
+ walkBeforeAfter(&n.Tag, before, after)
+ case *ast.FieldList:
+ for _, field := range n.List {
+ walkBeforeAfter(field, before, after)
+ }
+ case *ast.BadExpr:
+ case *ast.Ident:
+ case *ast.Ellipsis:
+ walkBeforeAfter(&n.Elt, before, after)
+ case *ast.BasicLit:
+ case *ast.FuncLit:
+ walkBeforeAfter(&n.Type, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.CompositeLit:
+ walkBeforeAfter(&n.Type, before, after)
+ walkBeforeAfter(&n.Elts, before, after)
+ case *ast.ParenExpr:
+ walkBeforeAfter(&n.X, before, after)
+ case *ast.SelectorExpr:
+ walkBeforeAfter(&n.X, before, after)
+ case *ast.IndexExpr:
+ walkBeforeAfter(&n.X, before, after)
+ walkBeforeAfter(&n.Index, before, after)
+ case *ast.IndexListExpr:
+ walkBeforeAfter(&n.X, before, after)
+ walkBeforeAfter(&n.Indices, before, after)
+ case *ast.SliceExpr:
+ walkBeforeAfter(&n.X, before, after)
+ if n.Low != nil {
+ walkBeforeAfter(&n.Low, before, after)
+ }
+ if n.High != nil {
+ walkBeforeAfter(&n.High, before, after)
+ }
+ case *ast.TypeAssertExpr:
+ walkBeforeAfter(&n.X, before, after)
+ walkBeforeAfter(&n.Type, before, after)
+ case *ast.CallExpr:
+ walkBeforeAfter(&n.Fun, before, after)
+ walkBeforeAfter(&n.Args, before, after)
+ case *ast.StarExpr:
+ walkBeforeAfter(&n.X, before, after)
+ case *ast.UnaryExpr:
+ walkBeforeAfter(&n.X, before, after)
+ case *ast.BinaryExpr:
+ walkBeforeAfter(&n.X, before, after)
+ walkBeforeAfter(&n.Y, before, after)
+ case *ast.KeyValueExpr:
+ walkBeforeAfter(&n.Key, before, after)
+ walkBeforeAfter(&n.Value, before, after)
+
+ case *ast.ArrayType:
+ walkBeforeAfter(&n.Len, before, after)
+ walkBeforeAfter(&n.Elt, before, after)
+ case *ast.StructType:
+ walkBeforeAfter(&n.Fields, before, after)
+ case *ast.FuncType:
+ if n.TypeParams != nil {
+ walkBeforeAfter(&n.TypeParams, before, after)
+ }
+ walkBeforeAfter(&n.Params, before, after)
+ if n.Results != nil {
+ walkBeforeAfter(&n.Results, before, after)
+ }
+ case *ast.InterfaceType:
+ walkBeforeAfter(&n.Methods, before, after)
+ case *ast.MapType:
+ walkBeforeAfter(&n.Key, before, after)
+ walkBeforeAfter(&n.Value, before, after)
+ case *ast.ChanType:
+ walkBeforeAfter(&n.Value, before, after)
+
+ case *ast.BadStmt:
+ case *ast.DeclStmt:
+ walkBeforeAfter(&n.Decl, before, after)
+ case *ast.EmptyStmt:
+ case *ast.LabeledStmt:
+ walkBeforeAfter(&n.Stmt, before, after)
+ case *ast.ExprStmt:
+ walkBeforeAfter(&n.X, before, after)
+ case *ast.SendStmt:
+ walkBeforeAfter(&n.Chan, before, after)
+ walkBeforeAfter(&n.Value, before, after)
+ case *ast.IncDecStmt:
+ walkBeforeAfter(&n.X, before, after)
+ case *ast.AssignStmt:
+ walkBeforeAfter(&n.Lhs, before, after)
+ walkBeforeAfter(&n.Rhs, before, after)
+ case *ast.GoStmt:
+ walkBeforeAfter(&n.Call, before, after)
+ case *ast.DeferStmt:
+ walkBeforeAfter(&n.Call, before, after)
+ case *ast.ReturnStmt:
+ walkBeforeAfter(&n.Results, before, after)
+ case *ast.BranchStmt:
+ case *ast.BlockStmt:
+ walkBeforeAfter(&n.List, before, after)
+ case *ast.IfStmt:
+ walkBeforeAfter(&n.Init, before, after)
+ walkBeforeAfter(&n.Cond, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ walkBeforeAfter(&n.Else, before, after)
+ case *ast.CaseClause:
+ walkBeforeAfter(&n.List, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.SwitchStmt:
+ walkBeforeAfter(&n.Init, before, after)
+ walkBeforeAfter(&n.Tag, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.TypeSwitchStmt:
+ walkBeforeAfter(&n.Init, before, after)
+ walkBeforeAfter(&n.Assign, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.CommClause:
+ walkBeforeAfter(&n.Comm, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.SelectStmt:
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.ForStmt:
+ walkBeforeAfter(&n.Init, before, after)
+ walkBeforeAfter(&n.Cond, before, after)
+ walkBeforeAfter(&n.Post, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+ case *ast.RangeStmt:
+ walkBeforeAfter(&n.Key, before, after)
+ walkBeforeAfter(&n.Value, before, after)
+ walkBeforeAfter(&n.X, before, after)
+ walkBeforeAfter(&n.Body, before, after)
+
+ case *ast.ImportSpec:
+ case *ast.ValueSpec:
+ walkBeforeAfter(&n.Type, before, after)
+ walkBeforeAfter(&n.Values, before, after)
+ walkBeforeAfter(&n.Names, before, after)
+ case *ast.TypeSpec:
+ if n.TypeParams != nil {
+ walkBeforeAfter(&n.TypeParams, before, after)
+ }
+ walkBeforeAfter(&n.Type, before, after)
+
+ case *ast.BadDecl:
+ case *ast.GenDecl:
+ walkBeforeAfter(&n.Specs, before, after)
+ case *ast.FuncDecl:
+ if n.Recv != nil {
+ walkBeforeAfter(&n.Recv, before, after)
+ }
+ walkBeforeAfter(&n.Type, before, after)
+ if n.Body != nil {
+ walkBeforeAfter(&n.Body, before, after)
+ }
+
+ case *ast.File:
+ walkBeforeAfter(&n.Decls, before, after)
+
+ case *ast.Package:
+ walkBeforeAfter(&n.Files, before, after)
+
+ case []*ast.File:
+ for i := range n {
+ walkBeforeAfter(&n[i], before, after)
+ }
+ case []ast.Decl:
+ for i := range n {
+ walkBeforeAfter(&n[i], before, after)
+ }
+ case []ast.Expr:
+ for i := range n {
+ walkBeforeAfter(&n[i], before, after)
+ }
+ case []*ast.Ident:
+ for i := range n {
+ walkBeforeAfter(&n[i], before, after)
+ }
+ case []ast.Stmt:
+ for i := range n {
+ walkBeforeAfter(&n[i], before, after)
+ }
+ case []ast.Spec:
+ for i := range n {
+ walkBeforeAfter(&n[i], before, after)
+ }
+ }
+ after(x)
+}
+
+// imports reports whether f imports path.
+func imports(f *ast.File, path string) bool {
+ return importSpec(f, path) != nil
+}
+
+// importSpec returns the import spec if f imports path,
+// or nil otherwise.
+func importSpec(f *ast.File, path string) *ast.ImportSpec {
+ for _, s := range f.Imports {
+ if importPath(s) == path {
+ return s
+ }
+ }
+ return nil
+}
+
+// importPath returns the unquoted import path of s,
+// or "" if the path is not properly quoted.
+func importPath(s *ast.ImportSpec) string {
+ t, err := strconv.Unquote(s.Path.Value)
+ if err == nil {
+ return t
+ }
+ return ""
+}
+
+// declImports reports whether gen contains an import of path.
+func declImports(gen *ast.GenDecl, path string) bool {
+ if gen.Tok != token.IMPORT {
+ return false
+ }
+ for _, spec := range gen.Specs {
+ impspec := spec.(*ast.ImportSpec)
+ if importPath(impspec) == path {
+ return true
+ }
+ }
+ return false
+}
+
+// isTopName reports whether n is a top-level unresolved identifier with the given name.
+func isTopName(n ast.Expr, name string) bool {
+ id, ok := n.(*ast.Ident)
+ return ok && id.Name == name && id.Obj == nil
+}
+
+// renameTop renames all references to the top-level name old.
+// It reports whether it makes any changes.
+func renameTop(f *ast.File, old, new string) bool {
+ var fixed bool
+
+ // Rename any conflicting imports
+ // (assuming package name is last element of path).
+ for _, s := range f.Imports {
+ if s.Name != nil {
+ if s.Name.Name == old {
+ s.Name.Name = new
+ fixed = true
+ }
+ } else {
+ _, thisName := path.Split(importPath(s))
+ if thisName == old {
+ s.Name = ast.NewIdent(new)
+ fixed = true
+ }
+ }
+ }
+
+ // Rename any top-level declarations.
+ for _, d := range f.Decls {
+ switch d := d.(type) {
+ case *ast.FuncDecl:
+ if d.Recv == nil && d.Name.Name == old {
+ d.Name.Name = new
+ d.Name.Obj.Name = new
+ fixed = true
+ }
+ case *ast.GenDecl:
+ for _, s := range d.Specs {
+ switch s := s.(type) {
+ case *ast.TypeSpec:
+ if s.Name.Name == old {
+ s.Name.Name = new
+ s.Name.Obj.Name = new
+ fixed = true
+ }
+ case *ast.ValueSpec:
+ for _, n := range s.Names {
+ if n.Name == old {
+ n.Name = new
+ n.Obj.Name = new
+ fixed = true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Rename top-level old to new, both unresolved names
+ // (probably defined in another file) and names that resolve
+ // to a declaration we renamed.
+ walk(f, func(n any) {
+ id, ok := n.(*ast.Ident)
+ if ok && isTopName(id, old) {
+ id.Name = new
+ fixed = true
+ }
+ if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new {
+ id.Name = id.Obj.Name
+ fixed = true
+ }
+ })
+
+ return fixed
+}
+
+// matchLen returns the length of the longest prefix shared by x and y.
+func matchLen(x, y string) int {
+ i := 0
+ for i < len(x) && i < len(y) && x[i] == y[i] {
+ i++
+ }
+ return i
+}
+
+// addImport adds the import path to the file f, if absent.
+func addImport(f *ast.File, ipath string) (added bool) {
+ if imports(f, ipath) {
+ return false
+ }
+
+ // Determine name of import.
+ // Assume added imports follow convention of using last element.
+ _, name := path.Split(ipath)
+
+ // Rename any conflicting top-level references from name to name_.
+ renameTop(f, name, name+"_")
+
+ newImport := &ast.ImportSpec{
+ Path: &ast.BasicLit{
+ Kind: token.STRING,
+ Value: strconv.Quote(ipath),
+ },
+ }
+
+ // Find an import decl to add to.
+ var (
+ bestMatch = -1
+ lastImport = -1
+ impDecl *ast.GenDecl
+ impIndex = -1
+ )
+ for i, decl := range f.Decls {
+ gen, ok := decl.(*ast.GenDecl)
+ if ok && gen.Tok == token.IMPORT {
+ lastImport = i
+ // Do not add to import "C", to avoid disrupting the
+ // association with its doc comment, breaking cgo.
+ if declImports(gen, "C") {
+ continue
+ }
+
+ // Compute longest shared prefix with imports in this block.
+ for j, spec := range gen.Specs {
+ impspec := spec.(*ast.ImportSpec)
+ n := matchLen(importPath(impspec), ipath)
+ if n > bestMatch {
+ bestMatch = n
+ impDecl = gen
+ impIndex = j
+ }
+ }
+ }
+ }
+
+ // If no import decl found, add one after the last import.
+ if impDecl == nil {
+ impDecl = &ast.GenDecl{
+ Tok: token.IMPORT,
+ }
+ f.Decls = append(f.Decls, nil)
+ copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
+ f.Decls[lastImport+1] = impDecl
+ }
+
+ // Ensure the import decl has parentheses, if needed.
+ if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() {
+ impDecl.Lparen = impDecl.Pos()
+ }
+
+ insertAt := impIndex + 1
+ if insertAt == 0 {
+ insertAt = len(impDecl.Specs)
+ }
+ impDecl.Specs = append(impDecl.Specs, nil)
+ copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
+ impDecl.Specs[insertAt] = newImport
+ if insertAt > 0 {
+ // Assign same position as the previous import,
+ // so that the sorter sees it as being in the same block.
+ prev := impDecl.Specs[insertAt-1]
+ newImport.Path.ValuePos = prev.Pos()
+ newImport.EndPos = prev.Pos()
+ }
+
+ f.Imports = append(f.Imports, newImport)
+ return true
+}
+
+// deleteImport deletes the import path from the file f, if present.
+func deleteImport(f *ast.File, path string) (deleted bool) {
+ oldImport := importSpec(f, path)
+
+ // Find the import node that imports path, if any.
+ for i, decl := range f.Decls {
+ gen, ok := decl.(*ast.GenDecl)
+ if !ok || gen.Tok != token.IMPORT {
+ continue
+ }
+ for j, spec := range gen.Specs {
+ impspec := spec.(*ast.ImportSpec)
+ if oldImport != impspec {
+ continue
+ }
+
+ // We found an import spec that imports path.
+ // Delete it.
+ deleted = true
+ copy(gen.Specs[j:], gen.Specs[j+1:])
+ gen.Specs = gen.Specs[:len(gen.Specs)-1]
+
+ // If this was the last import spec in this decl,
+ // delete the decl, too.
+ if len(gen.Specs) == 0 {
+ copy(f.Decls[i:], f.Decls[i+1:])
+ f.Decls = f.Decls[:len(f.Decls)-1]
+ } else if len(gen.Specs) == 1 {
+ gen.Lparen = token.NoPos // drop parens
+ }
+ if j > 0 {
+ // We deleted an entry but now there will be
+ // a blank line-sized hole where the import was.
+ // Close the hole by making the previous
+ // import appear to "end" where this one did.
+ gen.Specs[j-1].(*ast.ImportSpec).EndPos = impspec.End()
+ }
+ break
+ }
+ }
+
+ // Delete it from f.Imports.
+ for i, imp := range f.Imports {
+ if imp == oldImport {
+ copy(f.Imports[i:], f.Imports[i+1:])
+ f.Imports = f.Imports[:len(f.Imports)-1]
+ break
+ }
+ }
+
+ return
+}
+
+// rewriteImport rewrites any import of path oldPath to path newPath.
+func rewriteImport(f *ast.File, oldPath, newPath string) (rewrote bool) {
+ for _, imp := range f.Imports {
+ if importPath(imp) == oldPath {
+ rewrote = true
+ // record old End, because the default is to compute
+ // it using the length of imp.Path.Value.
+ imp.EndPos = imp.End()
+ imp.Path.Value = strconv.Quote(newPath)
+ }
+ }
+ return
+}
diff --git a/src/cmd/fix/gotypes.go b/src/cmd/fix/gotypes.go
new file mode 100644
index 0000000..6085816
--- /dev/null
+++ b/src/cmd/fix/gotypes.go
@@ -0,0 +1,75 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+ "strconv"
+)
+
+func init() {
+ register(gotypesFix)
+}
+
+var gotypesFix = fix{
+ name: "gotypes",
+ date: "2015-07-16",
+ f: gotypes,
+ desc: `Change imports of golang.org/x/tools/go/{exact,types} to go/{constant,types}`,
+}
+
+func gotypes(f *ast.File) bool {
+ fixed := fixGoTypes(f)
+ if fixGoExact(f) {
+ fixed = true
+ }
+ return fixed
+}
+
+func fixGoTypes(f *ast.File) bool {
+ return rewriteImport(f, "golang.org/x/tools/go/types", "go/types")
+}
+
+func fixGoExact(f *ast.File) bool {
+ // This one is harder because the import name changes.
+ // First find the import spec.
+ var importSpec *ast.ImportSpec
+ walk(f, func(n any) {
+ if importSpec != nil {
+ return
+ }
+ spec, ok := n.(*ast.ImportSpec)
+ if !ok {
+ return
+ }
+ path, err := strconv.Unquote(spec.Path.Value)
+ if err != nil {
+ return
+ }
+ if path == "golang.org/x/tools/go/exact" {
+ importSpec = spec
+ }
+
+ })
+ if importSpec == nil {
+ return false
+ }
+
+ // We are about to rename exact.* to constant.*, but constant is a common
+ // name. See if it will conflict. This is a hack but it is effective.
+ exists := renameTop(f, "constant", "constant")
+ suffix := ""
+ if exists {
+ suffix = "_"
+ }
+ // Now we need to rename all the uses of the import. RewriteImport
+ // affects renameTop, but not vice versa, so do them in this order.
+ renameTop(f, "exact", "constant"+suffix)
+ rewriteImport(f, "golang.org/x/tools/go/exact", "go/constant")
+ // renameTop will also rewrite the imported package name. Fix that;
+ // we know it should be missing.
+ importSpec.Name = nil
+ return true
+}
diff --git a/src/cmd/fix/gotypes_test.go b/src/cmd/fix/gotypes_test.go
new file mode 100644
index 0000000..9248fff
--- /dev/null
+++ b/src/cmd/fix/gotypes_test.go
@@ -0,0 +1,89 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(gotypesTests, gotypes)
+}
+
+var gotypesTests = []testCase{
+ {
+ Name: "gotypes.0",
+ In: `package main
+
+import "golang.org/x/tools/go/types"
+import "golang.org/x/tools/go/exact"
+
+var _ = exact.Kind
+
+func f() {
+ _ = exact.MakeBool(true)
+}
+`,
+ Out: `package main
+
+import "go/types"
+import "go/constant"
+
+var _ = constant.Kind
+
+func f() {
+ _ = constant.MakeBool(true)
+}
+`,
+ },
+ {
+ Name: "gotypes.1",
+ In: `package main
+
+import "golang.org/x/tools/go/types"
+import foo "golang.org/x/tools/go/exact"
+
+var _ = foo.Kind
+
+func f() {
+ _ = foo.MakeBool(true)
+}
+`,
+ Out: `package main
+
+import "go/types"
+import "go/constant"
+
+var _ = foo.Kind
+
+func f() {
+ _ = foo.MakeBool(true)
+}
+`,
+ },
+ {
+ Name: "gotypes.0",
+ In: `package main
+
+import "golang.org/x/tools/go/types"
+import "golang.org/x/tools/go/exact"
+
+var _ = exact.Kind
+var constant = 23 // Use of new package name.
+
+func f() {
+ _ = exact.MakeBool(true)
+}
+`,
+ Out: `package main
+
+import "go/types"
+import "go/constant"
+
+var _ = constant_.Kind
+var constant = 23 // Use of new package name.
+
+func f() {
+ _ = constant_.MakeBool(true)
+}
+`,
+ },
+}
diff --git a/src/cmd/fix/import_test.go b/src/cmd/fix/import_test.go
new file mode 100644
index 0000000..8644e28
--- /dev/null
+++ b/src/cmd/fix/import_test.go
@@ -0,0 +1,458 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "go/ast"
+
+func init() {
+ addTestCases(importTests, nil)
+}
+
+var importTests = []testCase{
+ {
+ Name: "import.0",
+ Fn: addImportFn("os"),
+ In: `package main
+
+import (
+ "os"
+)
+`,
+ Out: `package main
+
+import (
+ "os"
+)
+`,
+ },
+ {
+ Name: "import.1",
+ Fn: addImportFn("os"),
+ In: `package main
+`,
+ Out: `package main
+
+import "os"
+`,
+ },
+ {
+ Name: "import.2",
+ Fn: addImportFn("os"),
+ In: `package main
+
+// Comment
+import "C"
+`,
+ Out: `package main
+
+// Comment
+import "C"
+import "os"
+`,
+ },
+ {
+ Name: "import.3",
+ Fn: addImportFn("os"),
+ In: `package main
+
+// Comment
+import "C"
+
+import (
+ "io"
+ "utf8"
+)
+`,
+ Out: `package main
+
+// Comment
+import "C"
+
+import (
+ "io"
+ "os"
+ "utf8"
+)
+`,
+ },
+ {
+ Name: "import.4",
+ Fn: deleteImportFn("os"),
+ In: `package main
+
+import (
+ "os"
+)
+`,
+ Out: `package main
+`,
+ },
+ {
+ Name: "import.5",
+ Fn: deleteImportFn("os"),
+ In: `package main
+
+// Comment
+import "C"
+import "os"
+`,
+ Out: `package main
+
+// Comment
+import "C"
+`,
+ },
+ {
+ Name: "import.6",
+ Fn: deleteImportFn("os"),
+ In: `package main
+
+// Comment
+import "C"
+
+import (
+ "io"
+ "os"
+ "utf8"
+)
+`,
+ Out: `package main
+
+// Comment
+import "C"
+
+import (
+ "io"
+ "utf8"
+)
+`,
+ },
+ {
+ Name: "import.7",
+ Fn: deleteImportFn("io"),
+ In: `package main
+
+import (
+ "io" // a
+ "os" // b
+ "utf8" // c
+)
+`,
+ Out: `package main
+
+import (
+ // a
+ "os" // b
+ "utf8" // c
+)
+`,
+ },
+ {
+ Name: "import.8",
+ Fn: deleteImportFn("os"),
+ In: `package main
+
+import (
+ "io" // a
+ "os" // b
+ "utf8" // c
+)
+`,
+ Out: `package main
+
+import (
+ "io" // a
+ // b
+ "utf8" // c
+)
+`,
+ },
+ {
+ Name: "import.9",
+ Fn: deleteImportFn("utf8"),
+ In: `package main
+
+import (
+ "io" // a
+ "os" // b
+ "utf8" // c
+)
+`,
+ Out: `package main
+
+import (
+ "io" // a
+ "os" // b
+ // c
+)
+`,
+ },
+ {
+ Name: "import.10",
+ Fn: deleteImportFn("io"),
+ In: `package main
+
+import (
+ "io"
+ "os"
+ "utf8"
+)
+`,
+ Out: `package main
+
+import (
+ "os"
+ "utf8"
+)
+`,
+ },
+ {
+ Name: "import.11",
+ Fn: deleteImportFn("os"),
+ In: `package main
+
+import (
+ "io"
+ "os"
+ "utf8"
+)
+`,
+ Out: `package main
+
+import (
+ "io"
+ "utf8"
+)
+`,
+ },
+ {
+ Name: "import.12",
+ Fn: deleteImportFn("utf8"),
+ In: `package main
+
+import (
+ "io"
+ "os"
+ "utf8"
+)
+`,
+ Out: `package main
+
+import (
+ "io"
+ "os"
+)
+`,
+ },
+ {
+ Name: "import.13",
+ Fn: rewriteImportFn("utf8", "encoding/utf8"),
+ In: `package main
+
+import (
+ "io"
+ "os"
+ "utf8" // thanks ken
+)
+`,
+ Out: `package main
+
+import (
+ "encoding/utf8" // thanks ken
+ "io"
+ "os"
+)
+`,
+ },
+ {
+ Name: "import.14",
+ Fn: rewriteImportFn("asn1", "encoding/asn1"),
+ In: `package main
+
+import (
+ "asn1"
+ "crypto"
+ "crypto/rsa"
+ _ "crypto/sha1"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "time"
+)
+
+var x = 1
+`,
+ Out: `package main
+
+import (
+ "crypto"
+ "crypto/rsa"
+ _ "crypto/sha1"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "time"
+)
+
+var x = 1
+`,
+ },
+ {
+ Name: "import.15",
+ Fn: rewriteImportFn("url", "net/url"),
+ In: `package main
+
+import (
+ "bufio"
+ "net"
+ "path"
+ "url"
+)
+
+var x = 1 // comment on x, not on url
+`,
+ Out: `package main
+
+import (
+ "bufio"
+ "net"
+ "net/url"
+ "path"
+)
+
+var x = 1 // comment on x, not on url
+`,
+ },
+ {
+ Name: "import.16",
+ Fn: rewriteImportFn("http", "net/http", "template", "text/template"),
+ In: `package main
+
+import (
+ "flag"
+ "http"
+ "log"
+ "template"
+)
+
+var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
+`,
+ Out: `package main
+
+import (
+ "flag"
+ "log"
+ "net/http"
+ "text/template"
+)
+
+var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
+`,
+ },
+ {
+ Name: "import.17",
+ Fn: addImportFn("x/y/z", "x/a/c"),
+ In: `package main
+
+// Comment
+import "C"
+
+import (
+ "a"
+ "b"
+
+ "x/w"
+
+ "d/f"
+)
+`,
+ Out: `package main
+
+// Comment
+import "C"
+
+import (
+ "a"
+ "b"
+
+ "x/a/c"
+ "x/w"
+ "x/y/z"
+
+ "d/f"
+)
+`,
+ },
+ {
+ Name: "import.18",
+ Fn: addDelImportFn("e", "o"),
+ In: `package main
+
+import (
+ "f"
+ "o"
+ "z"
+)
+`,
+ Out: `package main
+
+import (
+ "e"
+ "f"
+ "z"
+)
+`,
+ },
+}
+
+func addImportFn(path ...string) func(*ast.File) bool {
+ return func(f *ast.File) bool {
+ fixed := false
+ for _, p := range path {
+ if !imports(f, p) {
+ addImport(f, p)
+ fixed = true
+ }
+ }
+ return fixed
+ }
+}
+
+func deleteImportFn(path string) func(*ast.File) bool {
+ return func(f *ast.File) bool {
+ if imports(f, path) {
+ deleteImport(f, path)
+ return true
+ }
+ return false
+ }
+}
+
+func addDelImportFn(p1 string, p2 string) func(*ast.File) bool {
+ return func(f *ast.File) bool {
+ fixed := false
+ if !imports(f, p1) {
+ addImport(f, p1)
+ fixed = true
+ }
+ if imports(f, p2) {
+ deleteImport(f, p2)
+ fixed = true
+ }
+ return fixed
+ }
+}
+
+func rewriteImportFn(oldnew ...string) func(*ast.File) bool {
+ return func(f *ast.File) bool {
+ fixed := false
+ for i := 0; i < len(oldnew); i += 2 {
+ if imports(f, oldnew[i]) {
+ rewriteImport(f, oldnew[i], oldnew[i+1])
+ fixed = true
+ }
+ }
+ return fixed
+ }
+}
diff --git a/src/cmd/fix/jnitype.go b/src/cmd/fix/jnitype.go
new file mode 100644
index 0000000..111be8e
--- /dev/null
+++ b/src/cmd/fix/jnitype.go
@@ -0,0 +1,69 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+)
+
+func init() {
+ register(jniFix)
+}
+
+var jniFix = fix{
+ name: "jni",
+ date: "2017-12-04",
+ f: jnifix,
+ desc: `Fixes initializers of JNI's jobject and subtypes`,
+ disabled: false,
+}
+
+// Old state:
+//
+// type jobject *_jobject
+//
+// New state:
+//
+// type jobject uintptr
+//
+// and similar for subtypes of jobject.
+// This fix finds nils initializing these types and replaces the nils with 0s.
+func jnifix(f *ast.File) bool {
+ return typefix(f, func(s string) bool {
+ switch s {
+ case "C.jobject":
+ return true
+ case "C.jclass":
+ return true
+ case "C.jthrowable":
+ return true
+ case "C.jstring":
+ return true
+ case "C.jarray":
+ return true
+ case "C.jbooleanArray":
+ return true
+ case "C.jbyteArray":
+ return true
+ case "C.jcharArray":
+ return true
+ case "C.jshortArray":
+ return true
+ case "C.jintArray":
+ return true
+ case "C.jlongArray":
+ return true
+ case "C.jfloatArray":
+ return true
+ case "C.jdoubleArray":
+ return true
+ case "C.jobjectArray":
+ return true
+ case "C.jweak":
+ return true
+ }
+ return false
+ })
+}
diff --git a/src/cmd/fix/jnitype_test.go b/src/cmd/fix/jnitype_test.go
new file mode 100644
index 0000000..ecf0140
--- /dev/null
+++ b/src/cmd/fix/jnitype_test.go
@@ -0,0 +1,203 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(jniTests, jnifix)
+}
+
+var jniTests = []testCase{
+ {
+ Name: "jni.localVariable",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+func f() {
+ var x C.jobject = nil
+ x = nil
+ x, x = nil, nil
+}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+func f() {
+ var x C.jobject = 0
+ x = 0
+ x, x = 0, 0
+}
+`,
+ },
+ {
+ Name: "jni.globalVariable",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x C.jobject = nil
+
+func f() {
+ x = nil
+}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x C.jobject = 0
+
+func f() {
+ x = 0
+}
+`,
+ },
+ {
+ Name: "jni.EqualArgument",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x C.jobject
+var y = x == nil
+var z = x != nil
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x C.jobject
+var y = x == 0
+var z = x != 0
+`,
+ },
+ {
+ Name: "jni.StructField",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+type T struct {
+ x C.jobject
+}
+
+var t = T{x: nil}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+type T struct {
+ x C.jobject
+}
+
+var t = T{x: 0}
+`,
+ },
+ {
+ Name: "jni.FunctionArgument",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+func f(x C.jobject) {
+}
+
+func g() {
+ f(nil)
+}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+func f(x C.jobject) {
+}
+
+func g() {
+ f(0)
+}
+`,
+ },
+ {
+ Name: "jni.ArrayElement",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = [3]C.jobject{nil, nil, nil}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = [3]C.jobject{0, 0, 0}
+`,
+ },
+ {
+ Name: "jni.SliceElement",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = []C.jobject{nil, nil, nil}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = []C.jobject{0, 0, 0}
+`,
+ },
+ {
+ Name: "jni.MapKey",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = map[C.jobject]int{nil: 0}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = map[C.jobject]int{0: 0}
+`,
+ },
+ {
+ Name: "jni.MapValue",
+ In: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = map[int]C.jobject{0: nil}
+`,
+ Out: `package main
+
+// typedef struct _jobject* jobject;
+import "C"
+
+var x = map[int]C.jobject{0: 0}
+`,
+ },
+}
diff --git a/src/cmd/fix/main.go b/src/cmd/fix/main.go
new file mode 100644
index 0000000..0f36fcc
--- /dev/null
+++ b/src/cmd/fix/main.go
@@ -0,0 +1,273 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "internal/diff"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+var (
+ fset = token.NewFileSet()
+ exitCode = 0
+)
+
+var allowedRewrites = flag.String("r", "",
+ "restrict the rewrites to this comma-separated list")
+
+var forceRewrites = flag.String("force", "",
+ "force these fixes to run even if the code looks updated")
+
+var allowed, force map[string]bool
+
+var (
+ doDiff = flag.Bool("diff", false, "display diffs instead of rewriting files")
+ goVersionStr = flag.String("go", "", "go language version for files")
+
+ goVersion int // 115 for go1.15
+)
+
+// enable for debugging fix failures
+const debug = false // display incorrectly reformatted source and exit
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go tool fix [-diff] [-r fixname,...] [-force fixname,...] [path ...]\n")
+ flag.PrintDefaults()
+ fmt.Fprintf(os.Stderr, "\nAvailable rewrites are:\n")
+ sort.Sort(byName(fixes))
+ for _, f := range fixes {
+ if f.disabled {
+ fmt.Fprintf(os.Stderr, "\n%s (disabled)\n", f.name)
+ } else {
+ fmt.Fprintf(os.Stderr, "\n%s\n", f.name)
+ }
+ desc := strings.TrimSpace(f.desc)
+ desc = strings.ReplaceAll(desc, "\n", "\n\t")
+ fmt.Fprintf(os.Stderr, "\t%s\n", desc)
+ }
+ os.Exit(2)
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+
+ if *goVersionStr != "" {
+ if !strings.HasPrefix(*goVersionStr, "go") {
+ report(fmt.Errorf("invalid -go=%s", *goVersionStr))
+ os.Exit(exitCode)
+ }
+ majorStr := (*goVersionStr)[len("go"):]
+ minorStr := "0"
+ if before, after, found := strings.Cut(majorStr, "."); found {
+ majorStr, minorStr = before, after
+ }
+ major, err1 := strconv.Atoi(majorStr)
+ minor, err2 := strconv.Atoi(minorStr)
+ if err1 != nil || err2 != nil || major < 0 || major >= 100 || minor < 0 || minor >= 100 {
+ report(fmt.Errorf("invalid -go=%s", *goVersionStr))
+ os.Exit(exitCode)
+ }
+
+ goVersion = major*100 + minor
+ }
+
+ sort.Sort(byDate(fixes))
+
+ if *allowedRewrites != "" {
+ allowed = make(map[string]bool)
+ for _, f := range strings.Split(*allowedRewrites, ",") {
+ allowed[f] = true
+ }
+ }
+
+ if *forceRewrites != "" {
+ force = make(map[string]bool)
+ for _, f := range strings.Split(*forceRewrites, ",") {
+ force[f] = true
+ }
+ }
+
+ if flag.NArg() == 0 {
+ if err := processFile("standard input", true); err != nil {
+ report(err)
+ }
+ os.Exit(exitCode)
+ }
+
+ for i := 0; i < flag.NArg(); i++ {
+ path := flag.Arg(i)
+ switch dir, err := os.Stat(path); {
+ case err != nil:
+ report(err)
+ case dir.IsDir():
+ walkDir(path)
+ default:
+ if err := processFile(path, false); err != nil {
+ report(err)
+ }
+ }
+ }
+
+ os.Exit(exitCode)
+}
+
+const parserMode = parser.ParseComments
+
+func gofmtFile(f *ast.File) ([]byte, error) {
+ var buf bytes.Buffer
+ if err := format.Node(&buf, fset, f); err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func processFile(filename string, useStdin bool) error {
+ var f *os.File
+ var err error
+ var fixlog strings.Builder
+
+ if useStdin {
+ f = os.Stdin
+ } else {
+ f, err = os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ }
+
+ src, err := io.ReadAll(f)
+ if err != nil {
+ return err
+ }
+
+ file, err := parser.ParseFile(fset, filename, src, parserMode)
+ if err != nil {
+ return err
+ }
+
+ // Make sure file is in canonical format.
+ // This "fmt" pseudo-fix cannot be disabled.
+ newSrc, err := gofmtFile(file)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(newSrc, src) {
+ newFile, err := parser.ParseFile(fset, filename, newSrc, parserMode)
+ if err != nil {
+ return err
+ }
+ file = newFile
+ fmt.Fprintf(&fixlog, " fmt")
+ }
+
+ // Apply all fixes to file.
+ newFile := file
+ fixed := false
+ for _, fix := range fixes {
+ if allowed != nil && !allowed[fix.name] {
+ continue
+ }
+ if fix.disabled && !force[fix.name] {
+ continue
+ }
+ if fix.f(newFile) {
+ fixed = true
+ fmt.Fprintf(&fixlog, " %s", fix.name)
+
+ // AST changed.
+ // Print and parse, to update any missing scoping
+ // or position information for subsequent fixers.
+ newSrc, err := gofmtFile(newFile)
+ if err != nil {
+ return err
+ }
+ newFile, err = parser.ParseFile(fset, filename, newSrc, parserMode)
+ if err != nil {
+ if debug {
+ fmt.Printf("%s", newSrc)
+ report(err)
+ os.Exit(exitCode)
+ }
+ return err
+ }
+ }
+ }
+ if !fixed {
+ return nil
+ }
+ fmt.Fprintf(os.Stderr, "%s: fixed %s\n", filename, fixlog.String()[1:])
+
+ // Print AST. We did that after each fix, so this appears
+ // redundant, but it is necessary to generate gofmt-compatible
+ // source code in a few cases. The official gofmt style is the
+ // output of the printer run on a standard AST generated by the parser,
+ // but the source we generated inside the loop above is the
+ // output of the printer run on a mangled AST generated by a fixer.
+ newSrc, err = gofmtFile(newFile)
+ if err != nil {
+ return err
+ }
+
+ if *doDiff {
+ os.Stdout.Write(diff.Diff(filename, src, "fixed/"+filename, newSrc))
+ return nil
+ }
+
+ if useStdin {
+ os.Stdout.Write(newSrc)
+ return nil
+ }
+
+ return os.WriteFile(f.Name(), newSrc, 0)
+}
+
+func gofmt(n any) string {
+ var gofmtBuf strings.Builder
+ if err := format.Node(&gofmtBuf, fset, n); err != nil {
+ return "<" + err.Error() + ">"
+ }
+ return gofmtBuf.String()
+}
+
+func report(err error) {
+ scanner.PrintError(os.Stderr, err)
+ exitCode = 2
+}
+
+func walkDir(path string) {
+ filepath.WalkDir(path, visitFile)
+}
+
+func visitFile(path string, f fs.DirEntry, err error) error {
+ if err == nil && isGoFile(f) {
+ err = processFile(path, false)
+ }
+ if err != nil {
+ report(err)
+ }
+ return nil
+}
+
+func isGoFile(f fs.DirEntry) bool {
+ // ignore non-Go files
+ name := f.Name()
+ return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
+}
diff --git a/src/cmd/fix/main_test.go b/src/cmd/fix/main_test.go
new file mode 100644
index 0000000..cafd116
--- /dev/null
+++ b/src/cmd/fix/main_test.go
@@ -0,0 +1,166 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "internal/diff"
+ "internal/testenv"
+ "strings"
+ "testing"
+)
+
+type testCase struct {
+ Name string
+ Fn func(*ast.File) bool
+ Version int
+ In string
+ Out string
+}
+
+var testCases []testCase
+
+func addTestCases(t []testCase, fn func(*ast.File) bool) {
+ // Fill in fn to avoid repetition in definitions.
+ if fn != nil {
+ for i := range t {
+ if t[i].Fn == nil {
+ t[i].Fn = fn
+ }
+ }
+ }
+ testCases = append(testCases, t...)
+}
+
+func fnop(*ast.File) bool { return false }
+
+func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string, mustBeGofmt bool) (out string, fixed, ok bool) {
+ file, err := parser.ParseFile(fset, desc, in, parserMode)
+ if err != nil {
+ t.Errorf("parsing: %v", err)
+ return
+ }
+
+ outb, err := gofmtFile(file)
+ if err != nil {
+ t.Errorf("printing: %v", err)
+ return
+ }
+ if s := string(outb); in != s && mustBeGofmt {
+ t.Errorf("not gofmt-formatted.\n--- %s\n%s\n--- %s | gofmt\n%s",
+ desc, in, desc, s)
+ tdiff(t, "want", in, "have", s)
+ return
+ }
+
+ if fn == nil {
+ for _, fix := range fixes {
+ if fix.f(file) {
+ fixed = true
+ }
+ }
+ } else {
+ fixed = fn(file)
+ }
+
+ outb, err = gofmtFile(file)
+ if err != nil {
+ t.Errorf("printing: %v", err)
+ return
+ }
+
+ return string(outb), fixed, true
+}
+
+func TestRewrite(t *testing.T) {
+ // If cgo is enabled, enforce that cgo commands invoked by cmd/fix
+ // do not fail during testing.
+ if testenv.HasCGO() {
+ testenv.MustHaveGoBuild(t) // Really just 'go tool cgo', but close enough.
+
+ // The reportCgoError hook is global, so we can't set it per-test
+ // if we want to be able to run those tests in parallel.
+ // Instead, simply set it to panic on error: the goroutine dump
+ // from the panic should help us determine which test failed.
+ prevReportCgoError := reportCgoError
+ reportCgoError = func(err error) {
+ panic(fmt.Sprintf("unexpected cgo error: %v", err))
+ }
+ t.Cleanup(func() { reportCgoError = prevReportCgoError })
+ }
+
+ for _, tt := range testCases {
+ tt := tt
+ t.Run(tt.Name, func(t *testing.T) {
+ if tt.Version == 0 {
+ if testing.Verbose() {
+ // Don't run in parallel: cmd/fix sometimes writes directly to stderr,
+ // and since -v prints which test is currently running we want that
+ // information to accurately correlate with the stderr output.
+ } else {
+ t.Parallel()
+ }
+ } else {
+ old := goVersion
+ goVersion = tt.Version
+ defer func() {
+ goVersion = old
+ }()
+ }
+
+ // Apply fix: should get tt.Out.
+ out, fixed, ok := parseFixPrint(t, tt.Fn, tt.Name, tt.In, true)
+ if !ok {
+ return
+ }
+
+ // reformat to get printing right
+ out, _, ok = parseFixPrint(t, fnop, tt.Name, out, false)
+ if !ok {
+ return
+ }
+
+ if tt.Out == "" {
+ tt.Out = tt.In
+ }
+ if out != tt.Out {
+ t.Errorf("incorrect output.\n")
+ if !strings.HasPrefix(tt.Name, "testdata/") {
+ t.Errorf("--- have\n%s\n--- want\n%s", out, tt.Out)
+ }
+ tdiff(t, "have", out, "want", tt.Out)
+ return
+ }
+
+ if changed := out != tt.In; changed != fixed {
+ t.Errorf("changed=%v != fixed=%v", changed, fixed)
+ return
+ }
+
+ // Should not change if run again.
+ out2, fixed2, ok := parseFixPrint(t, tt.Fn, tt.Name+" output", out, true)
+ if !ok {
+ return
+ }
+
+ if fixed2 {
+ t.Errorf("applied fixes during second round")
+ return
+ }
+
+ if out2 != out {
+ t.Errorf("changed output after second round of fixes.\n--- output after first round\n%s\n--- output after second round\n%s",
+ out, out2)
+ tdiff(t, "first", out, "second", out2)
+ }
+ })
+ }
+}
+
+func tdiff(t *testing.T, aname, a, bname, b string) {
+ t.Errorf("%s", diff.Diff(aname, []byte(a), bname, []byte(b)))
+}
diff --git a/src/cmd/fix/netipv6zone.go b/src/cmd/fix/netipv6zone.go
new file mode 100644
index 0000000..199fcf5
--- /dev/null
+++ b/src/cmd/fix/netipv6zone.go
@@ -0,0 +1,68 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "go/ast"
+
+func init() {
+ register(netipv6zoneFix)
+}
+
+var netipv6zoneFix = fix{
+ name: "netipv6zone",
+ date: "2012-11-26",
+ f: netipv6zone,
+ desc: `Adapt element key to IPAddr, UDPAddr or TCPAddr composite literals.
+
+https://codereview.appspot.com/6849045/
+`,
+}
+
+func netipv6zone(f *ast.File) bool {
+ if !imports(f, "net") {
+ return false
+ }
+
+ fixed := false
+ walk(f, func(n any) {
+ cl, ok := n.(*ast.CompositeLit)
+ if !ok {
+ return
+ }
+ se, ok := cl.Type.(*ast.SelectorExpr)
+ if !ok {
+ return
+ }
+ if !isTopName(se.X, "net") || se.Sel == nil {
+ return
+ }
+ switch ss := se.Sel.String(); ss {
+ case "IPAddr", "UDPAddr", "TCPAddr":
+ for i, e := range cl.Elts {
+ if _, ok := e.(*ast.KeyValueExpr); ok {
+ break
+ }
+ switch i {
+ case 0:
+ cl.Elts[i] = &ast.KeyValueExpr{
+ Key: ast.NewIdent("IP"),
+ Value: e,
+ }
+ case 1:
+ if elit, ok := e.(*ast.BasicLit); ok && elit.Value == "0" {
+ cl.Elts = append(cl.Elts[:i], cl.Elts[i+1:]...)
+ } else {
+ cl.Elts[i] = &ast.KeyValueExpr{
+ Key: ast.NewIdent("Port"),
+ Value: e,
+ }
+ }
+ }
+ fixed = true
+ }
+ }
+ })
+ return fixed
+}
diff --git a/src/cmd/fix/netipv6zone_test.go b/src/cmd/fix/netipv6zone_test.go
new file mode 100644
index 0000000..5b8d964
--- /dev/null
+++ b/src/cmd/fix/netipv6zone_test.go
@@ -0,0 +1,43 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(netipv6zoneTests, netipv6zone)
+}
+
+var netipv6zoneTests = []testCase{
+ {
+ Name: "netipv6zone.0",
+ In: `package main
+
+import "net"
+
+func f() net.Addr {
+ a := &net.IPAddr{ip1}
+ sub(&net.UDPAddr{ip2, 12345})
+ c := &net.TCPAddr{IP: ip3, Port: 54321}
+ d := &net.TCPAddr{ip4, 0}
+ p := 1234
+ e := &net.TCPAddr{ip4, p}
+ return &net.TCPAddr{ip5}, nil
+}
+`,
+ Out: `package main
+
+import "net"
+
+func f() net.Addr {
+ a := &net.IPAddr{IP: ip1}
+ sub(&net.UDPAddr{IP: ip2, Port: 12345})
+ c := &net.TCPAddr{IP: ip3, Port: 54321}
+ d := &net.TCPAddr{IP: ip4}
+ p := 1234
+ e := &net.TCPAddr{IP: ip4, Port: p}
+ return &net.TCPAddr{IP: ip5}, nil
+}
+`,
+ },
+}
diff --git a/src/cmd/fix/printerconfig.go b/src/cmd/fix/printerconfig.go
new file mode 100644
index 0000000..bad6953
--- /dev/null
+++ b/src/cmd/fix/printerconfig.go
@@ -0,0 +1,61 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "go/ast"
+
+func init() {
+ register(printerconfigFix)
+}
+
+var printerconfigFix = fix{
+ name: "printerconfig",
+ date: "2012-12-11",
+ f: printerconfig,
+ desc: `Add element keys to Config composite literals.`,
+}
+
+func printerconfig(f *ast.File) bool {
+ if !imports(f, "go/printer") {
+ return false
+ }
+
+ fixed := false
+ walk(f, func(n any) {
+ cl, ok := n.(*ast.CompositeLit)
+ if !ok {
+ return
+ }
+ se, ok := cl.Type.(*ast.SelectorExpr)
+ if !ok {
+ return
+ }
+ if !isTopName(se.X, "printer") || se.Sel == nil {
+ return
+ }
+
+ if ss := se.Sel.String(); ss == "Config" {
+ for i, e := range cl.Elts {
+ if _, ok := e.(*ast.KeyValueExpr); ok {
+ break
+ }
+ switch i {
+ case 0:
+ cl.Elts[i] = &ast.KeyValueExpr{
+ Key: ast.NewIdent("Mode"),
+ Value: e,
+ }
+ case 1:
+ cl.Elts[i] = &ast.KeyValueExpr{
+ Key: ast.NewIdent("Tabwidth"),
+ Value: e,
+ }
+ }
+ fixed = true
+ }
+ }
+ })
+ return fixed
+}
diff --git a/src/cmd/fix/printerconfig_test.go b/src/cmd/fix/printerconfig_test.go
new file mode 100644
index 0000000..e485c13
--- /dev/null
+++ b/src/cmd/fix/printerconfig_test.go
@@ -0,0 +1,37 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(printerconfigTests, printerconfig)
+}
+
+var printerconfigTests = []testCase{
+ {
+ Name: "printerconfig.0",
+ In: `package main
+
+import "go/printer"
+
+func f() printer.Config {
+ b := printer.Config{0, 8}
+ c := &printer.Config{0}
+ d := &printer.Config{Tabwidth: 8, Mode: 0}
+ return printer.Config{0, 8}
+}
+`,
+ Out: `package main
+
+import "go/printer"
+
+func f() printer.Config {
+ b := printer.Config{Mode: 0, Tabwidth: 8}
+ c := &printer.Config{Mode: 0}
+ d := &printer.Config{Tabwidth: 8, Mode: 0}
+ return printer.Config{Mode: 0, Tabwidth: 8}
+}
+`,
+ },
+}
diff --git a/src/cmd/fix/typecheck.go b/src/cmd/fix/typecheck.go
new file mode 100644
index 0000000..b115987
--- /dev/null
+++ b/src/cmd/fix/typecheck.go
@@ -0,0 +1,813 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+)
+
+// Partial type checker.
+//
+// The fact that it is partial is very important: the input is
+// an AST and a description of some type information to
+// assume about one or more packages, but not all the
+// packages that the program imports. The checker is
+// expected to do as much as it can with what it has been
+// given. There is not enough information supplied to do
+// a full type check, but the type checker is expected to
+// apply information that can be derived from variable
+// declarations, function and method returns, and type switches
+// as far as it can, so that the caller can still tell the types
+// of expression relevant to a particular fix.
+//
+// TODO(rsc,gri): Replace with go/typechecker.
+// Doing that could be an interesting test case for go/typechecker:
+// the constraints about working with partial information will
+// likely exercise it in interesting ways. The ideal interface would
+// be to pass typecheck a map from importpath to package API text
+// (Go source code), but for now we use data structures (TypeConfig, Type).
+//
+// The strings mostly use gofmt form.
+//
+// A Field or FieldList has as its type a comma-separated list
+// of the types of the fields. For example, the field list
+// x, y, z int
+// has type "int, int, int".
+
+// The prefix "type " is the type of a type.
+// For example, given
+// var x int
+// type T int
+// x's type is "int" but T's type is "type int".
+// mkType inserts the "type " prefix.
+// getType removes it.
+// isType tests for it.
+
+func mkType(t string) string {
+ return "type " + t
+}
+
+func getType(t string) string {
+ if !isType(t) {
+ return ""
+ }
+ return t[len("type "):]
+}
+
+func isType(t string) bool {
+ return strings.HasPrefix(t, "type ")
+}
+
+// TypeConfig describes the universe of relevant types.
+// For ease of creation, the types are all referred to by string
+// name (e.g., "reflect.Value"). TypeByName is the only place
+// where the strings are resolved.
+
+type TypeConfig struct {
+ Type map[string]*Type
+ Var map[string]string
+ Func map[string]string
+
+ // External maps from a name to its type.
+ // It provides additional typings not present in the Go source itself.
+ // For now, the only additional typings are those generated by cgo.
+ External map[string]string
+}
+
+// typeof returns the type of the given name, which may be of
+// the form "x" or "p.X".
+func (cfg *TypeConfig) typeof(name string) string {
+ if cfg.Var != nil {
+ if t := cfg.Var[name]; t != "" {
+ return t
+ }
+ }
+ if cfg.Func != nil {
+ if t := cfg.Func[name]; t != "" {
+ return "func()" + t
+ }
+ }
+ return ""
+}
+
+// Type describes the Fields and Methods of a type.
+// If the field or method cannot be found there, it is next
+// looked for in the Embed list.
+type Type struct {
+ Field map[string]string // map field name to type
+ Method map[string]string // map method name to comma-separated return types (should start with "func ")
+ Embed []string // list of types this type embeds (for extra methods)
+ Def string // definition of named type
+}
+
+// dot returns the type of "typ.name", making its decision
+// using the type information in cfg.
+func (typ *Type) dot(cfg *TypeConfig, name string) string {
+ if typ.Field != nil {
+ if t := typ.Field[name]; t != "" {
+ return t
+ }
+ }
+ if typ.Method != nil {
+ if t := typ.Method[name]; t != "" {
+ return t
+ }
+ }
+
+ for _, e := range typ.Embed {
+ etyp := cfg.Type[e]
+ if etyp != nil {
+ if t := etyp.dot(cfg, name); t != "" {
+ return t
+ }
+ }
+ }
+
+ return ""
+}
+
+// typecheck type checks the AST f assuming the information in cfg.
+// It returns two maps with type information:
+// typeof maps AST nodes to type information in gofmt string form.
+// assign maps type strings to lists of expressions that were assigned
+// to values of another type that were assigned to that type.
+func typecheck(cfg *TypeConfig, f *ast.File) (typeof map[any]string, assign map[string][]any) {
+ typeof = make(map[any]string)
+ assign = make(map[string][]any)
+ cfg1 := &TypeConfig{}
+ *cfg1 = *cfg // make copy so we can add locally
+ copied := false
+
+ // If we import "C", add types of cgo objects.
+ cfg.External = map[string]string{}
+ cfg1.External = cfg.External
+ if imports(f, "C") {
+ // Run cgo on gofmtFile(f)
+ // Parse, extract decls from _cgo_gotypes.go
+ // Map _Ctype_* types to C.* types.
+ err := func() error {
+ txt, err := gofmtFile(f)
+ if err != nil {
+ return err
+ }
+ dir, err := os.MkdirTemp(os.TempDir(), "fix_cgo_typecheck")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(dir)
+ err = os.WriteFile(filepath.Join(dir, "in.go"), txt, 0600)
+ if err != nil {
+ return err
+ }
+ goCmd := "go"
+ if goroot := runtime.GOROOT(); goroot != "" {
+ goCmd = filepath.Join(goroot, "bin", "go")
+ }
+ cmd := exec.Command(goCmd, "tool", "cgo", "-objdir", dir, "-srcdir", dir, "in.go")
+ if reportCgoError != nil {
+ // Since cgo command errors will be reported, also forward the error
+ // output from the command for debugging.
+ cmd.Stderr = os.Stderr
+ }
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+ out, err := os.ReadFile(filepath.Join(dir, "_cgo_gotypes.go"))
+ if err != nil {
+ return err
+ }
+ cgo, err := parser.ParseFile(token.NewFileSet(), "cgo.go", out, 0)
+ if err != nil {
+ return err
+ }
+ for _, decl := range cgo.Decls {
+ fn, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ if strings.HasPrefix(fn.Name.Name, "_Cfunc_") {
+ var params, results []string
+ for _, p := range fn.Type.Params.List {
+ t := gofmt(p.Type)
+ t = strings.ReplaceAll(t, "_Ctype_", "C.")
+ params = append(params, t)
+ }
+ for _, r := range fn.Type.Results.List {
+ t := gofmt(r.Type)
+ t = strings.ReplaceAll(t, "_Ctype_", "C.")
+ results = append(results, t)
+ }
+ cfg.External["C."+fn.Name.Name[7:]] = joinFunc(params, results)
+ }
+ }
+ return nil
+ }()
+ if err != nil {
+ if reportCgoError == nil {
+ fmt.Fprintf(os.Stderr, "go fix: warning: no cgo types: %s\n", err)
+ } else {
+ reportCgoError(err)
+ }
+ }
+ }
+
+ // gather function declarations
+ for _, decl := range f.Decls {
+ fn, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ typecheck1(cfg, fn.Type, typeof, assign)
+ t := typeof[fn.Type]
+ if fn.Recv != nil {
+ // The receiver must be a type.
+ rcvr := typeof[fn.Recv]
+ if !isType(rcvr) {
+ if len(fn.Recv.List) != 1 {
+ continue
+ }
+ rcvr = mkType(gofmt(fn.Recv.List[0].Type))
+ typeof[fn.Recv.List[0].Type] = rcvr
+ }
+ rcvr = getType(rcvr)
+ if rcvr != "" && rcvr[0] == '*' {
+ rcvr = rcvr[1:]
+ }
+ typeof[rcvr+"."+fn.Name.Name] = t
+ } else {
+ if isType(t) {
+ t = getType(t)
+ } else {
+ t = gofmt(fn.Type)
+ }
+ typeof[fn.Name] = t
+
+ // Record typeof[fn.Name.Obj] for future references to fn.Name.
+ typeof[fn.Name.Obj] = t
+ }
+ }
+
+ // gather struct declarations
+ for _, decl := range f.Decls {
+ d, ok := decl.(*ast.GenDecl)
+ if ok {
+ for _, s := range d.Specs {
+ switch s := s.(type) {
+ case *ast.TypeSpec:
+ if cfg1.Type[s.Name.Name] != nil {
+ break
+ }
+ if !copied {
+ copied = true
+ // Copy map lazily: it's time.
+ cfg1.Type = make(map[string]*Type)
+ for k, v := range cfg.Type {
+ cfg1.Type[k] = v
+ }
+ }
+ t := &Type{Field: map[string]string{}}
+ cfg1.Type[s.Name.Name] = t
+ switch st := s.Type.(type) {
+ case *ast.StructType:
+ for _, f := range st.Fields.List {
+ for _, n := range f.Names {
+ t.Field[n.Name] = gofmt(f.Type)
+ }
+ }
+ case *ast.ArrayType, *ast.StarExpr, *ast.MapType:
+ t.Def = gofmt(st)
+ }
+ }
+ }
+ }
+ }
+
+ typecheck1(cfg1, f, typeof, assign)
+ return typeof, assign
+}
+
+// reportCgoError, if non-nil, reports a non-nil error from running the "cgo"
+// tool. (Set to a non-nil hook during testing if cgo is expected to work.)
+var reportCgoError func(err error)
+
+func makeExprList(a []*ast.Ident) []ast.Expr {
+ var b []ast.Expr
+ for _, x := range a {
+ b = append(b, x)
+ }
+ return b
+}
+
+// typecheck1 is the recursive form of typecheck.
+// It is like typecheck but adds to the information in typeof
+// instead of allocating a new map.
+func typecheck1(cfg *TypeConfig, f any, typeof map[any]string, assign map[string][]any) {
+ // set sets the type of n to typ.
+ // If isDecl is true, n is being declared.
+ set := func(n ast.Expr, typ string, isDecl bool) {
+ if typeof[n] != "" || typ == "" {
+ if typeof[n] != typ {
+ assign[typ] = append(assign[typ], n)
+ }
+ return
+ }
+ typeof[n] = typ
+
+ // If we obtained typ from the declaration of x
+ // propagate the type to all the uses.
+ // The !isDecl case is a cheat here, but it makes
+ // up in some cases for not paying attention to
+ // struct fields. The real type checker will be
+ // more accurate so we won't need the cheat.
+ if id, ok := n.(*ast.Ident); ok && id.Obj != nil && (isDecl || typeof[id.Obj] == "") {
+ typeof[id.Obj] = typ
+ }
+ }
+
+ // Type-check an assignment lhs = rhs.
+ // If isDecl is true, this is := so we can update
+ // the types of the objects that lhs refers to.
+ typecheckAssign := func(lhs, rhs []ast.Expr, isDecl bool) {
+ if len(lhs) > 1 && len(rhs) == 1 {
+ if _, ok := rhs[0].(*ast.CallExpr); ok {
+ t := split(typeof[rhs[0]])
+ // Lists should have same length but may not; pair what can be paired.
+ for i := 0; i < len(lhs) && i < len(t); i++ {
+ set(lhs[i], t[i], isDecl)
+ }
+ return
+ }
+ }
+ if len(lhs) == 1 && len(rhs) == 2 {
+ // x = y, ok
+ rhs = rhs[:1]
+ } else if len(lhs) == 2 && len(rhs) == 1 {
+ // x, ok = y
+ lhs = lhs[:1]
+ }
+
+ // Match as much as we can.
+ for i := 0; i < len(lhs) && i < len(rhs); i++ {
+ x, y := lhs[i], rhs[i]
+ if typeof[y] != "" {
+ set(x, typeof[y], isDecl)
+ } else {
+ set(y, typeof[x], false)
+ }
+ }
+ }
+
+ expand := func(s string) string {
+ typ := cfg.Type[s]
+ if typ != nil && typ.Def != "" {
+ return typ.Def
+ }
+ return s
+ }
+
+ // The main type check is a recursive algorithm implemented
+ // by walkBeforeAfter(n, before, after).
+ // Most of it is bottom-up, but in a few places we need
+ // to know the type of the function we are checking.
+ // The before function records that information on
+ // the curfn stack.
+ var curfn []*ast.FuncType
+
+ before := func(n any) {
+ // push function type on stack
+ switch n := n.(type) {
+ case *ast.FuncDecl:
+ curfn = append(curfn, n.Type)
+ case *ast.FuncLit:
+ curfn = append(curfn, n.Type)
+ }
+ }
+
+ // After is the real type checker.
+ after := func(n any) {
+ if n == nil {
+ return
+ }
+ if false && reflect.TypeOf(n).Kind() == reflect.Pointer { // debugging trace
+ defer func() {
+ if t := typeof[n]; t != "" {
+ pos := fset.Position(n.(ast.Node).Pos())
+ fmt.Fprintf(os.Stderr, "%s: typeof[%s] = %s\n", pos, gofmt(n), t)
+ }
+ }()
+ }
+
+ switch n := n.(type) {
+ case *ast.FuncDecl, *ast.FuncLit:
+ // pop function type off stack
+ curfn = curfn[:len(curfn)-1]
+
+ case *ast.FuncType:
+ typeof[n] = mkType(joinFunc(split(typeof[n.Params]), split(typeof[n.Results])))
+
+ case *ast.FieldList:
+ // Field list is concatenation of sub-lists.
+ t := ""
+ for _, field := range n.List {
+ if t != "" {
+ t += ", "
+ }
+ t += typeof[field]
+ }
+ typeof[n] = t
+
+ case *ast.Field:
+ // Field is one instance of the type per name.
+ all := ""
+ t := typeof[n.Type]
+ if !isType(t) {
+ // Create a type, because it is typically *T or *p.T
+ // and we might care about that type.
+ t = mkType(gofmt(n.Type))
+ typeof[n.Type] = t
+ }
+ t = getType(t)
+ if len(n.Names) == 0 {
+ all = t
+ } else {
+ for _, id := range n.Names {
+ if all != "" {
+ all += ", "
+ }
+ all += t
+ typeof[id.Obj] = t
+ typeof[id] = t
+ }
+ }
+ typeof[n] = all
+
+ case *ast.ValueSpec:
+ // var declaration. Use type if present.
+ if n.Type != nil {
+ t := typeof[n.Type]
+ if !isType(t) {
+ t = mkType(gofmt(n.Type))
+ typeof[n.Type] = t
+ }
+ t = getType(t)
+ for _, id := range n.Names {
+ set(id, t, true)
+ }
+ }
+ // Now treat same as assignment.
+ typecheckAssign(makeExprList(n.Names), n.Values, true)
+
+ case *ast.AssignStmt:
+ typecheckAssign(n.Lhs, n.Rhs, n.Tok == token.DEFINE)
+
+ case *ast.Ident:
+ // Identifier can take its type from underlying object.
+ if t := typeof[n.Obj]; t != "" {
+ typeof[n] = t
+ }
+
+ case *ast.SelectorExpr:
+ // Field or method.
+ name := n.Sel.Name
+ if t := typeof[n.X]; t != "" {
+ t = strings.TrimPrefix(t, "*") // implicit *
+ if typ := cfg.Type[t]; typ != nil {
+ if t := typ.dot(cfg, name); t != "" {
+ typeof[n] = t
+ return
+ }
+ }
+ tt := typeof[t+"."+name]
+ if isType(tt) {
+ typeof[n] = getType(tt)
+ return
+ }
+ }
+ // Package selector.
+ if x, ok := n.X.(*ast.Ident); ok && x.Obj == nil {
+ str := x.Name + "." + name
+ if cfg.Type[str] != nil {
+ typeof[n] = mkType(str)
+ return
+ }
+ if t := cfg.typeof(x.Name + "." + name); t != "" {
+ typeof[n] = t
+ return
+ }
+ }
+
+ case *ast.CallExpr:
+ // make(T) has type T.
+ if isTopName(n.Fun, "make") && len(n.Args) >= 1 {
+ typeof[n] = gofmt(n.Args[0])
+ return
+ }
+ // new(T) has type *T
+ if isTopName(n.Fun, "new") && len(n.Args) == 1 {
+ typeof[n] = "*" + gofmt(n.Args[0])
+ return
+ }
+ // Otherwise, use type of function to determine arguments.
+ t := typeof[n.Fun]
+ if t == "" {
+ t = cfg.External[gofmt(n.Fun)]
+ }
+ in, out := splitFunc(t)
+ if in == nil && out == nil {
+ return
+ }
+ typeof[n] = join(out)
+ for i, arg := range n.Args {
+ if i >= len(in) {
+ break
+ }
+ if typeof[arg] == "" {
+ typeof[arg] = in[i]
+ }
+ }
+
+ case *ast.TypeAssertExpr:
+ // x.(type) has type of x.
+ if n.Type == nil {
+ typeof[n] = typeof[n.X]
+ return
+ }
+ // x.(T) has type T.
+ if t := typeof[n.Type]; isType(t) {
+ typeof[n] = getType(t)
+ } else {
+ typeof[n] = gofmt(n.Type)
+ }
+
+ case *ast.SliceExpr:
+ // x[i:j] has type of x.
+ typeof[n] = typeof[n.X]
+
+ case *ast.IndexExpr:
+ // x[i] has key type of x's type.
+ t := expand(typeof[n.X])
+ if strings.HasPrefix(t, "[") || strings.HasPrefix(t, "map[") {
+ // Lazy: assume there are no nested [] in the array
+ // length or map key type.
+ if _, elem, ok := strings.Cut(t, "]"); ok {
+ typeof[n] = elem
+ }
+ }
+
+ case *ast.StarExpr:
+ // *x for x of type *T has type T when x is an expr.
+ // We don't use the result when *x is a type, but
+ // compute it anyway.
+ t := expand(typeof[n.X])
+ if isType(t) {
+ typeof[n] = "type *" + getType(t)
+ } else if strings.HasPrefix(t, "*") {
+ typeof[n] = t[len("*"):]
+ }
+
+ case *ast.UnaryExpr:
+ // &x for x of type T has type *T.
+ t := typeof[n.X]
+ if t != "" && n.Op == token.AND {
+ typeof[n] = "*" + t
+ }
+
+ case *ast.CompositeLit:
+ // T{...} has type T.
+ typeof[n] = gofmt(n.Type)
+
+ // Propagate types down to values used in the composite literal.
+ t := expand(typeof[n])
+ if strings.HasPrefix(t, "[") { // array or slice
+ // Lazy: assume there are no nested [] in the array length.
+ if _, et, ok := strings.Cut(t, "]"); ok {
+ for _, e := range n.Elts {
+ if kv, ok := e.(*ast.KeyValueExpr); ok {
+ e = kv.Value
+ }
+ if typeof[e] == "" {
+ typeof[e] = et
+ }
+ }
+ }
+ }
+ if strings.HasPrefix(t, "map[") { // map
+ // Lazy: assume there are no nested [] in the map key type.
+ if kt, vt, ok := strings.Cut(t[len("map["):], "]"); ok {
+ for _, e := range n.Elts {
+ if kv, ok := e.(*ast.KeyValueExpr); ok {
+ if typeof[kv.Key] == "" {
+ typeof[kv.Key] = kt
+ }
+ if typeof[kv.Value] == "" {
+ typeof[kv.Value] = vt
+ }
+ }
+ }
+ }
+ }
+ if typ := cfg.Type[t]; typ != nil && len(typ.Field) > 0 { // struct
+ for _, e := range n.Elts {
+ if kv, ok := e.(*ast.KeyValueExpr); ok {
+ if ft := typ.Field[fmt.Sprintf("%s", kv.Key)]; ft != "" {
+ if typeof[kv.Value] == "" {
+ typeof[kv.Value] = ft
+ }
+ }
+ }
+ }
+ }
+
+ case *ast.ParenExpr:
+ // (x) has type of x.
+ typeof[n] = typeof[n.X]
+
+ case *ast.RangeStmt:
+ t := expand(typeof[n.X])
+ if t == "" {
+ return
+ }
+ var key, value string
+ if t == "string" {
+ key, value = "int", "rune"
+ } else if strings.HasPrefix(t, "[") {
+ key = "int"
+ _, value, _ = strings.Cut(t, "]")
+ } else if strings.HasPrefix(t, "map[") {
+ if k, v, ok := strings.Cut(t[len("map["):], "]"); ok {
+ key, value = k, v
+ }
+ }
+ changed := false
+ if n.Key != nil && key != "" {
+ changed = true
+ set(n.Key, key, n.Tok == token.DEFINE)
+ }
+ if n.Value != nil && value != "" {
+ changed = true
+ set(n.Value, value, n.Tok == token.DEFINE)
+ }
+ // Ugly failure of vision: already type-checked body.
+ // Do it again now that we have that type info.
+ if changed {
+ typecheck1(cfg, n.Body, typeof, assign)
+ }
+
+ case *ast.TypeSwitchStmt:
+ // Type of variable changes for each case in type switch,
+ // but go/parser generates just one variable.
+ // Repeat type check for each case with more precise
+ // type information.
+ as, ok := n.Assign.(*ast.AssignStmt)
+ if !ok {
+ return
+ }
+ varx, ok := as.Lhs[0].(*ast.Ident)
+ if !ok {
+ return
+ }
+ t := typeof[varx]
+ for _, cas := range n.Body.List {
+ cas := cas.(*ast.CaseClause)
+ if len(cas.List) == 1 {
+ // Variable has specific type only when there is
+ // exactly one type in the case list.
+ if tt := typeof[cas.List[0]]; isType(tt) {
+ tt = getType(tt)
+ typeof[varx] = tt
+ typeof[varx.Obj] = tt
+ typecheck1(cfg, cas.Body, typeof, assign)
+ }
+ }
+ }
+ // Restore t.
+ typeof[varx] = t
+ typeof[varx.Obj] = t
+
+ case *ast.ReturnStmt:
+ if len(curfn) == 0 {
+ // Probably can't happen.
+ return
+ }
+ f := curfn[len(curfn)-1]
+ res := n.Results
+ if f.Results != nil {
+ t := split(typeof[f.Results])
+ for i := 0; i < len(res) && i < len(t); i++ {
+ set(res[i], t[i], false)
+ }
+ }
+
+ case *ast.BinaryExpr:
+ // Propagate types across binary ops that require two args of the same type.
+ switch n.Op {
+ case token.EQL, token.NEQ: // TODO: more cases. This is enough for the cftype fix.
+ if typeof[n.X] != "" && typeof[n.Y] == "" {
+ typeof[n.Y] = typeof[n.X]
+ }
+ if typeof[n.X] == "" && typeof[n.Y] != "" {
+ typeof[n.X] = typeof[n.Y]
+ }
+ }
+ }
+ }
+ walkBeforeAfter(f, before, after)
+}
+
+// Convert between function type strings and lists of types.
+// Using strings makes this a little harder, but it makes
+// a lot of the rest of the code easier. This will all go away
+// when we can use go/typechecker directly.
+
+// splitFunc splits "func(x,y,z) (a,b,c)" into ["x", "y", "z"] and ["a", "b", "c"].
+func splitFunc(s string) (in, out []string) {
+ if !strings.HasPrefix(s, "func(") {
+ return nil, nil
+ }
+
+ i := len("func(") // index of beginning of 'in' arguments
+ nparen := 0
+ for j := i; j < len(s); j++ {
+ switch s[j] {
+ case '(':
+ nparen++
+ case ')':
+ nparen--
+ if nparen < 0 {
+ // found end of parameter list
+ out := strings.TrimSpace(s[j+1:])
+ if len(out) >= 2 && out[0] == '(' && out[len(out)-1] == ')' {
+ out = out[1 : len(out)-1]
+ }
+ return split(s[i:j]), split(out)
+ }
+ }
+ }
+ return nil, nil
+}
+
+// joinFunc is the inverse of splitFunc.
+func joinFunc(in, out []string) string {
+ outs := ""
+ if len(out) == 1 {
+ outs = " " + out[0]
+ } else if len(out) > 1 {
+ outs = " (" + join(out) + ")"
+ }
+ return "func(" + join(in) + ")" + outs
+}
+
+// split splits "int, float" into ["int", "float"] and splits "" into [].
+func split(s string) []string {
+ out := []string{}
+ i := 0 // current type being scanned is s[i:j].
+ nparen := 0
+ for j := 0; j < len(s); j++ {
+ switch s[j] {
+ case ' ':
+ if i == j {
+ i++
+ }
+ case '(':
+ nparen++
+ case ')':
+ nparen--
+ if nparen < 0 {
+ // probably can't happen
+ return nil
+ }
+ case ',':
+ if nparen == 0 {
+ if i < j {
+ out = append(out, s[i:j])
+ }
+ i = j + 1
+ }
+ }
+ }
+ if nparen != 0 {
+ // probably can't happen
+ return nil
+ }
+ if i < len(s) {
+ out = append(out, s[i:])
+ }
+ return out
+}
+
+// join is the inverse of split.
+func join(x []string) string {
+ return strings.Join(x, ", ")
+}