+++ /dev/null
-// 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"
- "go/version"
- "strings"
-)
-
-func init() {
- register(buildtagFix)
-}
-
-const buildtagGoVersionCutoff = "go1.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 version.Compare(*goVersion, buildtagGoVersionCutoff) < 0 {
- 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
-}
+++ /dev/null
-// 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: "go1.10",
- In: `//go:build yes
-// +build yes
-
-package main
-`,
- },
- {
- Name: "buildtag.new",
- Version: "go1.99",
- In: `//go:build yes
-// +build yes
-
-package main
-`,
- Out: `//go:build yes
-
-package main
-`,
- },
-}
+++ /dev/null
-// 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(cftypeFix)
-}
-
-var cftypeFix = fix{
- name: "cftype",
- date: "2017-09-27",
- f: noop,
- desc: `Fixes initializers and casts of C.*Ref and JNI types (removed)`,
- disabled: false,
-}
-
-func noop(f *ast.File) bool {
- return false
-}
+++ /dev/null
-// 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() {
- register(contextFix)
-}
-
-var contextFix = fix{
- name: "context",
- date: "2016-09-09",
- f: noop,
- desc: `Change imports of golang.org/x/net/context to context (removed)`,
- disabled: false,
-}
Usage:
- go tool fix [-r name,...] [path ...]
+ go tool fix [ignored...]
-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.
+This tool is currently in transition. All its historical fixers were
+long obsolete and have been removed, so it is currently a no-op. In
+due course the tool will integrate with the Go analysis framework
+(golang.org/x/tools/go/analysis) and run a modern suite of fix
+algorithms; see https://go.dev/issue/71859.
*/
package main
+++ /dev/null
-// 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
-
-func init() {
- register(eglFixDisplay)
- register(eglFixConfig)
-}
-
-var eglFixDisplay = fix{
- name: "egl",
- date: "2018-12-15",
- f: noop,
- desc: `Fixes initializers of EGLDisplay (removed)`,
- disabled: false,
-}
-
-var eglFixConfig = fix{
- name: "eglconf",
- date: "2020-05-30",
- f: noop,
- desc: `Fixes initializers of EGLConfig (removed)`,
- disabled: false,
-}
+++ /dev/null
-// 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
-}
-
-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
-}
+++ /dev/null
-// 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
-
-func init() {
- register(gotypesFix)
-}
-
-var gotypesFix = fix{
- name: "gotypes",
- date: "2015-07-16",
- f: noop,
- desc: `Change imports of golang.org/x/tools/go/{exact,types} to go/{constant,types} (removed)`,
-}
+++ /dev/null
-// 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
- }
-}
+++ /dev/null
-// 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() {
- register(jniFix)
-}
-
-var jniFix = fix{
- name: "jni",
- date: "2017-12-04",
- f: noop,
- desc: `Fixes initializers of JNI's jobject and subtypes (removed)`,
- disabled: false,
-}
package main
import (
- "bytes"
"flag"
"fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/scanner"
- "go/token"
- "go/version"
- "internal/diff"
- "io"
- "io/fs"
"os"
- "path/filepath"
- "slices"
- "strings"
-
- "cmd/internal/telemetry/counter"
)
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")
- goVersion = flag.String("go", "", "go language version for files")
+ _ = flag.Bool("diff", false, "obsolete, no effect")
+ _ = flag.String("go", "", "obsolete, no effect")
+ _ = flag.String("r", "", "obsolete, no effect")
+ _ = flag.String("force", "", "obsolete, no effect")
)
-// 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")
+ fmt.Fprintf(os.Stderr, "usage: go tool fix [-diff] [-r ignored] [-force ignored] ...\n")
flag.PrintDefaults()
- fmt.Fprintf(os.Stderr, "\nAvailable rewrites are:\n")
- slices.SortFunc(fixes, func(a, b fix) int {
- return strings.Compare(a.name, b.name)
- })
- 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() {
- counter.Open()
flag.Usage = usage
flag.Parse()
- counter.Inc("fix/invocations")
- counter.CountFlags("fix/flag:", *flag.CommandLine)
-
- if !version.IsValid(*goVersion) {
- report(fmt.Errorf("invalid -go=%s", *goVersion))
- os.Exit(exitCode)
- }
-
- slices.SortFunc(fixes, func(a, b fix) int {
- return strings.Compare(a.date, b.date)
- })
-
- if *allowedRewrites != "" {
- allowed = make(map[string]bool)
- for f := range strings.SplitSeq(*allowedRewrites, ",") {
- allowed[f] = true
- }
- }
-
- if *forceRewrites != "" {
- force = make(map[string]bool)
- for f := range strings.SplitSeq(*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")
+ os.Exit(0)
}
+++ /dev/null
-// 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 string
- 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 == "" {
- 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)))
-}
+++ /dev/null
-// 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() {
- register(netipv6zoneFix)
-}
-
-var netipv6zoneFix = fix{
- name: "netipv6zone",
- date: "2012-11-26",
- f: noop,
- desc: `Adapt element key to IPAddr, UDPAddr or TCPAddr composite literals (removed).
-
-https://codereview.appspot.com/6849045/
-`,
-}
+++ /dev/null
-// 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() {
- register(printerconfigFix)
-}
-
-var printerconfigFix = fix{
- name: "printerconfig",
- date: "2012-12-11",
- f: noop,
- desc: `Add element keys to Config composite literals (removed).`,
-}
+++ /dev/null
-// 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"
- "maps"
- "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 = maps.Clone(cfg.Type)
- if cfg1.Type == nil {
- cfg1.Type = make(map[string]*Type)
- }
- }
- 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, ", ")
-}