diff --git a/src/cmd/fix/buildtag.go b/src/cmd/fix/buildtag.go deleted file mode 100644 index 6b706c4cb5a..00000000000 --- a/src/cmd/fix/buildtag.go +++ /dev/null @@ -1,52 +0,0 @@ -// 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 -} diff --git a/src/cmd/fix/buildtag_test.go b/src/cmd/fix/buildtag_test.go deleted file mode 100644 index e5997043c21..00000000000 --- a/src/cmd/fix/buildtag_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// 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 -`, - }, -} diff --git a/src/cmd/fix/cftype.go b/src/cmd/fix/cftype.go deleted file mode 100644 index 3e9f4c5a35c..00000000000 --- a/src/cmd/fix/cftype.go +++ /dev/null @@ -1,25 +0,0 @@ -// 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 -} diff --git a/src/cmd/fix/context.go b/src/cmd/fix/context.go deleted file mode 100644 index fe2e0950520..00000000000 --- a/src/cmd/fix/context.go +++ /dev/null @@ -1,17 +0,0 @@ -// 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, -} diff --git a/src/cmd/fix/doc.go b/src/cmd/fix/doc.go index 062eb792856..b3d69144717 100644 --- a/src/cmd/fix/doc.go +++ b/src/cmd/fix/doc.go @@ -9,29 +9,12 @@ the necessary changes to your programs. 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 diff --git a/src/cmd/fix/egltype.go b/src/cmd/fix/egltype.go deleted file mode 100644 index 8ba66efb062..00000000000 --- a/src/cmd/fix/egltype.go +++ /dev/null @@ -1,26 +0,0 @@ -// 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, -} diff --git a/src/cmd/fix/fix.go b/src/cmd/fix/fix.go deleted file mode 100644 index 26adae41ee4..00000000000 --- a/src/cmd/fix/fix.go +++ /dev/null @@ -1,552 +0,0 @@ -// 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 -} diff --git a/src/cmd/fix/gotypes.go b/src/cmd/fix/gotypes.go deleted file mode 100644 index 987dab5d028..00000000000 --- a/src/cmd/fix/gotypes.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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)`, -} diff --git a/src/cmd/fix/import_test.go b/src/cmd/fix/import_test.go deleted file mode 100644 index 8644e28f850..00000000000 --- a/src/cmd/fix/import_test.go +++ /dev/null @@ -1,458 +0,0 @@ -// 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 deleted file mode 100644 index bee38e67200..00000000000 --- a/src/cmd/fix/jnitype.go +++ /dev/null @@ -1,17 +0,0 @@ -// 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, -} diff --git a/src/cmd/fix/main.go b/src/cmd/fix/main.go index 933c32bcd92..87cc0d64146 100644 --- a/src/cmd/fix/main.go +++ b/src/cmd/fix/main.go @@ -5,261 +5,27 @@ 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 + _ = 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") ) -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") -) - -// 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) } diff --git a/src/cmd/fix/main_test.go b/src/cmd/fix/main_test.go deleted file mode 100644 index 8d841b101fe..00000000000 --- a/src/cmd/fix/main_test.go +++ /dev/null @@ -1,166 +0,0 @@ -// 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))) -} diff --git a/src/cmd/fix/netipv6zone.go b/src/cmd/fix/netipv6zone.go deleted file mode 100644 index 75d2150e43c..00000000000 --- a/src/cmd/fix/netipv6zone.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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/ -`, -} diff --git a/src/cmd/fix/printerconfig.go b/src/cmd/fix/printerconfig.go deleted file mode 100644 index f9e49d7c0bd..00000000000 --- a/src/cmd/fix/printerconfig.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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).`, -} diff --git a/src/cmd/fix/typecheck.go b/src/cmd/fix/typecheck.go deleted file mode 100644 index be21582fce3..00000000000 --- a/src/cmd/fix/typecheck.go +++ /dev/null @@ -1,814 +0,0 @@ -// 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, ", ") -}