1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // This file implements a custom generator to create various go/types
6 // source files from the corresponding types2 files.
25 var filesToWrite = flag.String("write", "", `go/types files to generate, or "all" for all files`)
28 srcDir = "/src/cmd/compile/internal/types2/"
29 dstDir = "/src/go/types/"
32 // TestGenerate verifies that generated files in go/types match their types2
33 // counterpart. If -write is set, this test actually writes the expected
34 // content to go/types; otherwise, it just compares with the existing content.
35 func TestGenerate(t *testing.T) {
36 // If filesToWrite is set, write the generated content to disk.
37 // In the special case of "all", write all files in filemap.
38 write := *filesToWrite != ""
39 var files []string // files to process
40 if *filesToWrite != "" && *filesToWrite != "all" {
41 files = strings.Split(*filesToWrite, ",")
43 for file := range filemap {
44 files = append(files, file)
48 for _, filename := range files {
49 generate(t, filename, write)
53 func generate(t *testing.T, filename string, write bool) {
55 srcFilename := filepath.FromSlash(runtime.GOROOT() + srcDir + filename)
56 file, err := parser.ParseFile(fset, srcFilename, nil, parser.ParseComments)
62 file.Name.Name = strings.ReplaceAll(file.Name.Name, "types2", "types")
64 // rewrite AST as needed
65 if action := filemap[filename]; action != nil {
71 buf.WriteString("// Code generated by \"go test -run=Generate -write=all\"; DO NOT EDIT.\n\n")
72 if err := format.Node(&buf, fset, file); err != nil {
75 generatedContent := buf.Bytes()
77 dstFilename := filepath.FromSlash(runtime.GOROOT() + dstDir + filename)
78 onDiskContent, err := os.ReadFile(dstFilename)
80 t.Fatalf("reading %q: %v", filename, err)
83 if d := diff.Diff(filename+" (on disk)", onDiskContent, filename+" (generated)", generatedContent); d != nil {
85 t.Logf("applying change:\n%s", d)
86 if err := os.WriteFile(dstFilename, generatedContent, 0o644); err != nil {
87 t.Fatalf("writing %q: %v", filename, err)
90 t.Errorf("generated file content does not match:\n%s", string(d))
95 type action func(in *ast.File)
97 var filemap = map[string]action{
102 "const.go": func(f *ast.File) { fixTokenPos(f) },
104 "context_test.go": nil,
105 "gccgosizes.go": nil,
106 "gcsizes.go": func(f *ast.File) { renameIdent(f, "IsSyncAtomicAlign64", "_IsSyncAtomicAlign64") },
107 "hilbert_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) },
108 "infer.go": func(f *ast.File) {
112 // "initorder.go": fixErrErrorfCall, // disabled for now due to unresolved error_ use implications for gopls
113 "instantiate.go": func(f *ast.File) { fixTokenPos(f); fixCheckErrorfCall(f) },
114 "instantiate_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) },
115 "lookup.go": func(f *ast.File) { fixTokenPos(f) },
118 "named.go": func(f *ast.File) { fixTokenPos(f); fixTraceSel(f) },
119 "object.go": func(f *ast.File) { fixTokenPos(f); renameIdent(f, "NewTypeNameLazy", "_NewTypeNameLazy") },
120 "object_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) },
124 "predicates.go": nil,
125 "scope.go": func(f *ast.File) {
127 renameIdent(f, "Squash", "squash")
128 renameIdent(f, "InsertLazy", "_InsertLazy")
131 "sizes.go": func(f *ast.File) { renameIdent(f, "IsSyncAtomicAlign64", "_IsSyncAtomicAlign64") },
133 "subst.go": func(f *ast.File) { fixTokenPos(f); fixTraceSel(f) },
135 "termlist_test.go": nil,
139 "typeterm_test.go": nil,
142 "unify.go": fixSprintf,
143 "universe.go": fixGlobalTypVarDecl,
144 "util_test.go": fixTokenPos,
146 "version_test.go": nil,
149 // TODO(gri) We should be able to make these rewriters more configurable/composable.
150 // For now this is a good starting point.
152 // renameIdent renames an identifier.
153 // Note: This doesn't change the use of the identifier in comments.
154 func renameIdent(f *ast.File, from, to string) {
155 ast.Inspect(f, func(n ast.Node) bool {
156 switch n := n.(type) {
167 // renameImportPath renames an import path.
168 func renameImportPath(f *ast.File, from, to string) {
169 ast.Inspect(f, func(n ast.Node) bool {
170 switch n := n.(type) {
171 case *ast.ImportSpec:
172 if n.Path.Kind == token.STRING && n.Path.Value == from {
181 // fixTokenPos changes imports of "cmd/compile/internal/syntax" to "go/token",
182 // uses of syntax.Pos to token.Pos, and calls to x.IsKnown() to x.IsValid().
183 func fixTokenPos(f *ast.File) {
184 ast.Inspect(f, func(n ast.Node) bool {
185 switch n := n.(type) {
186 case *ast.ImportSpec:
187 // rewrite import path "cmd/compile/internal/syntax" to "go/token"
188 if n.Path.Kind == token.STRING && n.Path.Value == `"cmd/compile/internal/syntax"` {
189 n.Path.Value = `"go/token"`
192 case *ast.SelectorExpr:
193 // rewrite syntax.Pos to token.Pos
194 if x, _ := n.X.(*ast.Ident); x != nil && x.Name == "syntax" && n.Sel.Name == "Pos" {
199 // rewrite x.IsKnown() to x.IsValid()
200 if fun, _ := n.Fun.(*ast.SelectorExpr); fun != nil && fun.Sel.Name == "IsKnown" && len(n.Args) == 0 {
201 fun.Sel.Name = "IsValid"
209 // fixInferSig updates the Checker.infer signature to use a positioner instead of a token.Position
210 // as first argument, renames the argument from "pos" to "posn", and updates a few internal uses of
211 // "pos" to "posn" and "posn.Pos()" respectively.
212 func fixInferSig(f *ast.File) {
213 ast.Inspect(f, func(n ast.Node) bool {
214 switch n := n.(type) {
216 if n.Name.Name == "infer" {
217 // rewrite (pos token.Pos, ...) to (posn positioner, ...)
218 par := n.Type.Params.List[0]
219 if len(par.Names) == 1 && par.Names[0].Name == "pos" {
220 par.Names[0] = newIdent(par.Names[0].Pos(), "posn")
221 par.Type = newIdent(par.Type.Pos(), "positioner")
226 if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil {
227 switch selx.Sel.Name {
228 case "renameTParams":
229 // rewrite check.renameTParams(pos, ... ) to check.renameTParams(posn.Pos(), ... )
230 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "pos" {
231 pos := n.Args[0].Pos()
232 fun := &ast.SelectorExpr{X: newIdent(pos, "posn"), Sel: newIdent(pos, "Pos")}
233 arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: nil, Ellipsis: token.NoPos, Rparen: pos}
238 // rewrite check.errorf(pos, ...) to check.errorf(posn, ...)
239 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "pos" {
240 pos := n.Args[0].Pos()
241 arg := newIdent(pos, "posn")
246 // rewrite check.allowVersion(..., pos, ...) to check.allowVersion(..., posn, ...)
247 if ident, _ := n.Args[1].(*ast.Ident); ident != nil && ident.Name == "pos" {
248 pos := n.Args[1].Pos()
249 arg := newIdent(pos, "posn")
260 // fixErrErrorfCall updates calls of the form err.errorf(obj, ...) to err.errorf(obj.Pos(), ...).
261 func fixErrErrorfCall(f *ast.File) {
262 ast.Inspect(f, func(n ast.Node) bool {
263 switch n := n.(type) {
265 if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil {
266 if ident, _ := selx.X.(*ast.Ident); ident != nil && ident.Name == "err" {
267 switch selx.Sel.Name {
269 // rewrite err.errorf(obj, ... ) to err.errorf(obj.Pos(), ... )
270 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "obj" {
271 pos := n.Args[0].Pos()
272 fun := &ast.SelectorExpr{X: ident, Sel: newIdent(pos, "Pos")}
273 arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: nil, Ellipsis: token.NoPos, Rparen: pos}
285 // fixCheckErrorfCall updates calls of the form check.errorf(pos, ...) to check.errorf(atPos(pos), ...).
286 func fixCheckErrorfCall(f *ast.File) {
287 ast.Inspect(f, func(n ast.Node) bool {
288 switch n := n.(type) {
290 if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil {
291 if ident, _ := selx.X.(*ast.Ident); ident != nil && ident.Name == "check" {
292 switch selx.Sel.Name {
294 // rewrite check.errorf(pos, ... ) to check.errorf(atPos(pos), ... )
295 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "pos" {
296 pos := n.Args[0].Pos()
297 fun := newIdent(pos, "atPos")
298 arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: []ast.Expr{ident}, Ellipsis: token.NoPos, Rparen: pos}
310 // fixTraceSel renames uses of x.Trace to x.trace, where x for any x with a Trace field.
311 func fixTraceSel(f *ast.File) {
312 ast.Inspect(f, func(n ast.Node) bool {
313 switch n := n.(type) {
314 case *ast.SelectorExpr:
315 // rewrite x.Trace to x._Trace (for Config.Trace)
316 if n.Sel.Name == "Trace" {
317 n.Sel.Name = "_Trace"
325 // fixGlobalTypVarDecl changes the global Typ variable from an array to a slice
326 // (in types2 we use an array for efficiency, in go/types it's a slice and we
327 // cannot change that).
328 func fixGlobalTypVarDecl(f *ast.File) {
329 ast.Inspect(f, func(n ast.Node) bool {
330 switch n := n.(type) {
332 // rewrite type Typ = [...]Type{...} to type Typ = []Type{...}
333 if len(n.Names) == 1 && n.Names[0].Name == "Typ" && len(n.Values) == 1 {
334 n.Values[0].(*ast.CompositeLit).Type.(*ast.ArrayType).Len = nil
342 // fixSprintf adds an extra nil argument for the *token.FileSet parameter in sprintf calls.
343 func fixSprintf(f *ast.File) {
344 ast.Inspect(f, func(n ast.Node) bool {
345 switch n := n.(type) {
347 if fun, _ := n.Fun.(*ast.Ident); fun != nil && fun.Name == "sprintf" && len(n.Args) >= 4 /* ... args */ {
348 n.Args = insert(n.Args, 1, newIdent(n.Args[1].Pos(), "nil"))
356 // newIdent returns a new identifier with the given position and name.
357 func newIdent(pos token.Pos, name string) *ast.Ident {
358 id := ast.NewIdent(name)
363 // insert inserts x at list[at] and moves the remaining elements up.
364 func insert(list []ast.Expr, at int, x ast.Expr) []ast.Expr {
365 list = append(list, nil)
366 copy(list[at+1:], list[at:])