]> Cypherpunks.ru repositories - gostls13.git/blob - src/go/types/generate_test.go
runtime: prevent send on closed channel in wakeableSleep
[gostls13.git] / src / go / types / generate_test.go
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.
4
5 // This file implements a custom generator to create various go/types
6 // source files from the corresponding types2 files.
7
8 package types_test
9
10 import (
11         "bytes"
12         "flag"
13         "go/ast"
14         "go/format"
15         "go/parser"
16         "go/token"
17         "internal/diff"
18         "os"
19         "path/filepath"
20         "runtime"
21         "strings"
22         "testing"
23 )
24
25 var filesToWrite = flag.String("write", "", `go/types files to generate, or "all" for all files`)
26
27 const (
28         srcDir = "/src/cmd/compile/internal/types2/"
29         dstDir = "/src/go/types/"
30 )
31
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, ",")
42         } else {
43                 for file := range filemap {
44                         files = append(files, file)
45                 }
46         }
47
48         for _, filename := range files {
49                 generate(t, filename, write)
50         }
51 }
52
53 func generate(t *testing.T, filename string, write bool) {
54         // parse src
55         srcFilename := filepath.FromSlash(runtime.GOROOT() + srcDir + filename)
56         file, err := parser.ParseFile(fset, srcFilename, nil, parser.ParseComments)
57         if err != nil {
58                 t.Fatal(err)
59         }
60
61         // fix package name
62         file.Name.Name = strings.ReplaceAll(file.Name.Name, "types2", "types")
63
64         // rewrite AST as needed
65         if action := filemap[filename]; action != nil {
66                 action(file)
67         }
68
69         // format AST
70         var buf bytes.Buffer
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 {
73                 t.Fatal(err)
74         }
75         generatedContent := buf.Bytes()
76
77         dstFilename := filepath.FromSlash(runtime.GOROOT() + dstDir + filename)
78         onDiskContent, err := os.ReadFile(dstFilename)
79         if err != nil {
80                 t.Fatalf("reading %q: %v", filename, err)
81         }
82
83         if d := diff.Diff(filename+" (on disk)", onDiskContent, filename+" (generated)", generatedContent); d != nil {
84                 if write {
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)
88                         }
89                 } else {
90                         t.Errorf("generated file content does not match:\n%s", string(d))
91                 }
92         }
93 }
94
95 type action func(in *ast.File)
96
97 var filemap = map[string]action{
98         "alias.go":        nil,
99         "array.go":        nil,
100         "basic.go":        nil,
101         "chan.go":         nil,
102         "const.go":        func(f *ast.File) { fixTokenPos(f) },
103         "context.go":      nil,
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) {
109                 fixTokenPos(f)
110                 fixInferSig(f)
111         },
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) },
116         "main_test.go":        nil,
117         "map.go":              nil,
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"`) },
121         "objset.go":           nil,
122         "package.go":          nil,
123         "pointer.go":          nil,
124         "predicates.go":       nil,
125         "scope.go": func(f *ast.File) {
126                 fixTokenPos(f)
127                 renameIdent(f, "Squash", "squash")
128                 renameIdent(f, "InsertLazy", "_InsertLazy")
129         },
130         "selection.go":     nil,
131         "sizes.go":         func(f *ast.File) { renameIdent(f, "IsSyncAtomicAlign64", "_IsSyncAtomicAlign64") },
132         "slice.go":         nil,
133         "subst.go":         func(f *ast.File) { fixTokenPos(f); fixTraceSel(f) },
134         "termlist.go":      nil,
135         "termlist_test.go": nil,
136         "tuple.go":         nil,
137         "typelists.go":     nil,
138         "typeparam.go":     nil,
139         "typeterm_test.go": nil,
140         "typeterm.go":      nil,
141         "under.go":         nil,
142         "unify.go":         fixSprintf,
143         "universe.go":      fixGlobalTypVarDecl,
144         "util_test.go":     fixTokenPos,
145         "validtype.go":     nil,
146         "version_test.go":  nil,
147 }
148
149 // TODO(gri) We should be able to make these rewriters more configurable/composable.
150 //           For now this is a good starting point.
151
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) {
157                 case *ast.Ident:
158                         if n.Name == from {
159                                 n.Name = to
160                         }
161                         return false
162                 }
163                 return true
164         })
165 }
166
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 {
173                                 n.Path.Value = to
174                                 return false
175                         }
176                 }
177                 return true
178         })
179 }
180
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"`
190                                 return false
191                         }
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" {
195                                 x.Name = "token"
196                                 return false
197                         }
198                 case *ast.CallExpr:
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"
202                                 return false
203                         }
204                 }
205                 return true
206         })
207 }
208
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) {
215                 case *ast.FuncDecl:
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")
222                                         return true
223                                 }
224                         }
225                 case *ast.CallExpr:
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}
234                                                 n.Args[0] = arg
235                                                 return false
236                                         }
237                                 case "errorf":
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")
242                                                 n.Args[0] = arg
243                                                 return false
244                                         }
245                                 case "allowVersion":
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")
250                                                 n.Args[1] = arg
251                                                 return false
252                                         }
253                                 }
254                         }
255                 }
256                 return true
257         })
258 }
259
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) {
264                 case *ast.CallExpr:
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 {
268                                         case "errorf":
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}
274                                                         n.Args[0] = arg
275                                                         return false
276                                                 }
277                                         }
278                                 }
279                         }
280                 }
281                 return true
282         })
283 }
284
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) {
289                 case *ast.CallExpr:
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 {
293                                         case "errorf":
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}
299                                                         n.Args[0] = arg
300                                                         return false
301                                                 }
302                                         }
303                                 }
304                         }
305                 }
306                 return true
307         })
308 }
309
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"
318                                 return false
319                         }
320                 }
321                 return true
322         })
323 }
324
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) {
331                 case *ast.ValueSpec:
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
335                                 return false
336                         }
337                 }
338                 return true
339         })
340 }
341
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) {
346                 case *ast.CallExpr:
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"))
349                                 return false
350                         }
351                 }
352                 return true
353         })
354 }
355
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)
359         id.NamePos = pos
360         return id
361 }
362
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:])
367         list[at] = x
368         return list
369 }