github.com/AndrienkoAleksandr/go@v0.0.19/src/go/types/generate_test.go (about) 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 "array.go": nil, 99 "basic.go": nil, 100 "chan.go": nil, 101 "const.go": func(f *ast.File) { fixTokenPos(f) }, 102 "context.go": nil, 103 "context_test.go": nil, 104 "gccgosizes.go": nil, 105 "hilbert_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) }, 106 "infer.go": func(f *ast.File) { 107 fixTokenPos(f) 108 fixInferSig(f) 109 }, 110 // "initorder.go": fixErrErrorfCall, // disabled for now due to unresolved error_ use implications for gopls 111 "instantiate.go": func(f *ast.File) { fixTokenPos(f); fixCheckErrorfCall(f) }, 112 "instantiate_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) }, 113 "lookup.go": func(f *ast.File) { fixTokenPos(f) }, 114 "main_test.go": nil, 115 "map.go": nil, 116 "named.go": func(f *ast.File) { fixTokenPos(f); fixTraceSel(f) }, 117 "object.go": func(f *ast.File) { fixTokenPos(f); renameIdent(f, "NewTypeNameLazy", "_NewTypeNameLazy") }, 118 "object_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) }, 119 "objset.go": nil, 120 "package.go": nil, 121 "pointer.go": nil, 122 "predicates.go": nil, 123 "scope.go": func(f *ast.File) { 124 fixTokenPos(f) 125 renameIdent(f, "Squash", "squash") 126 renameIdent(f, "InsertLazy", "_InsertLazy") 127 }, 128 "selection.go": nil, 129 "sizes.go": func(f *ast.File) { renameIdent(f, "IsSyncAtomicAlign64", "_IsSyncAtomicAlign64") }, 130 "slice.go": nil, 131 "subst.go": func(f *ast.File) { fixTokenPos(f); fixTraceSel(f) }, 132 "termlist.go": nil, 133 "termlist_test.go": nil, 134 "tuple.go": nil, 135 "typelists.go": nil, 136 "typeparam.go": nil, 137 "typeterm_test.go": nil, 138 "typeterm.go": nil, 139 "under.go": nil, 140 "unify.go": fixSprintf, 141 "universe.go": fixGlobalTypVarDecl, 142 "util_test.go": fixTokenPos, 143 "validtype.go": nil, 144 } 145 146 // TODO(gri) We should be able to make these rewriters more configurable/composable. 147 // For now this is a good starting point. 148 149 // renameIdent renames an identifier. 150 // Note: This doesn't change the use of the identifier in comments. 151 func renameIdent(f *ast.File, from, to string) { 152 ast.Inspect(f, func(n ast.Node) bool { 153 switch n := n.(type) { 154 case *ast.Ident: 155 if n.Name == from { 156 n.Name = to 157 } 158 return false 159 } 160 return true 161 }) 162 } 163 164 // renameImportPath renames an import path. 165 func renameImportPath(f *ast.File, from, to string) { 166 ast.Inspect(f, func(n ast.Node) bool { 167 switch n := n.(type) { 168 case *ast.ImportSpec: 169 if n.Path.Kind == token.STRING && n.Path.Value == from { 170 n.Path.Value = to 171 return false 172 } 173 } 174 return true 175 }) 176 } 177 178 // fixTokenPos changes imports of "cmd/compile/internal/syntax" to "go/token", 179 // uses of syntax.Pos to token.Pos, and calls to x.IsKnown() to x.IsValid(). 180 func fixTokenPos(f *ast.File) { 181 ast.Inspect(f, func(n ast.Node) bool { 182 switch n := n.(type) { 183 case *ast.ImportSpec: 184 // rewrite import path "cmd/compile/internal/syntax" to "go/token" 185 if n.Path.Kind == token.STRING && n.Path.Value == `"cmd/compile/internal/syntax"` { 186 n.Path.Value = `"go/token"` 187 return false 188 } 189 case *ast.SelectorExpr: 190 // rewrite syntax.Pos to token.Pos 191 if x, _ := n.X.(*ast.Ident); x != nil && x.Name == "syntax" && n.Sel.Name == "Pos" { 192 x.Name = "token" 193 return false 194 } 195 case *ast.CallExpr: 196 // rewrite x.IsKnown() to x.IsValid() 197 if fun, _ := n.Fun.(*ast.SelectorExpr); fun != nil && fun.Sel.Name == "IsKnown" && len(n.Args) == 0 { 198 fun.Sel.Name = "IsValid" 199 return false 200 } 201 } 202 return true 203 }) 204 } 205 206 // fixInferSig updates the Checker.infer signature to use a positioner instead of a token.Position 207 // as first argument, renames the argument from "pos" to "posn", and updates a few internal uses of 208 // "pos" to "posn" and "posn.Pos()" respectively. 209 func fixInferSig(f *ast.File) { 210 ast.Inspect(f, func(n ast.Node) bool { 211 switch n := n.(type) { 212 case *ast.FuncDecl: 213 if n.Name.Name == "infer" || n.Name.Name == "infer1" || n.Name.Name == "infer2" { 214 // rewrite (pos token.Pos, ...) to (posn positioner, ...) 215 par := n.Type.Params.List[0] 216 if len(par.Names) == 1 && par.Names[0].Name == "pos" { 217 par.Names[0] = newIdent(par.Names[0].Pos(), "posn") 218 par.Type = newIdent(par.Type.Pos(), "positioner") 219 return true 220 } 221 } 222 case *ast.CallExpr: 223 if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil { 224 switch selx.Sel.Name { 225 case "renameTParams": 226 // rewrite check.renameTParams(pos, ... ) to check.renameTParams(posn.Pos(), ... ) 227 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "pos" { 228 pos := n.Args[0].Pos() 229 fun := &ast.SelectorExpr{X: newIdent(pos, "posn"), Sel: newIdent(pos, "Pos")} 230 arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: nil, Ellipsis: token.NoPos, Rparen: pos} 231 n.Args[0] = arg 232 return false 233 } 234 case "errorf", "infer1", "infer2": 235 // rewrite check.errorf(pos, ...) to check.errorf(posn, ...) 236 // rewrite check.infer1(pos, ...) to check.infer1(posn, ...) 237 // rewrite check.infer2(pos, ...) to check.infer2(posn, ...) 238 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "pos" { 239 pos := n.Args[0].Pos() 240 arg := newIdent(pos, "posn") 241 n.Args[0] = arg 242 return false 243 } 244 } 245 } 246 } 247 return true 248 }) 249 } 250 251 // fixErrErrorfCall updates calls of the form err.errorf(obj, ...) to err.errorf(obj.Pos(), ...). 252 func fixErrErrorfCall(f *ast.File) { 253 ast.Inspect(f, func(n ast.Node) bool { 254 switch n := n.(type) { 255 case *ast.CallExpr: 256 if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil { 257 if ident, _ := selx.X.(*ast.Ident); ident != nil && ident.Name == "err" { 258 switch selx.Sel.Name { 259 case "errorf": 260 // rewrite err.errorf(obj, ... ) to err.errorf(obj.Pos(), ... ) 261 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "obj" { 262 pos := n.Args[0].Pos() 263 fun := &ast.SelectorExpr{X: ident, Sel: newIdent(pos, "Pos")} 264 arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: nil, Ellipsis: token.NoPos, Rparen: pos} 265 n.Args[0] = arg 266 return false 267 } 268 } 269 } 270 } 271 } 272 return true 273 }) 274 } 275 276 // fixCheckErrorfCall updates calls of the form check.errorf(pos, ...) to check.errorf(atPos(pos), ...). 277 func fixCheckErrorfCall(f *ast.File) { 278 ast.Inspect(f, func(n ast.Node) bool { 279 switch n := n.(type) { 280 case *ast.CallExpr: 281 if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil { 282 if ident, _ := selx.X.(*ast.Ident); ident != nil && ident.Name == "check" { 283 switch selx.Sel.Name { 284 case "errorf": 285 // rewrite check.errorf(pos, ... ) to check.errorf(atPos(pos), ... ) 286 if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "pos" { 287 pos := n.Args[0].Pos() 288 fun := newIdent(pos, "atPos") 289 arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: []ast.Expr{ident}, Ellipsis: token.NoPos, Rparen: pos} 290 n.Args[0] = arg 291 return false 292 } 293 } 294 } 295 } 296 } 297 return true 298 }) 299 } 300 301 // fixTraceSel renames uses of x.Trace to x.trace, where x for any x with a Trace field. 302 func fixTraceSel(f *ast.File) { 303 ast.Inspect(f, func(n ast.Node) bool { 304 switch n := n.(type) { 305 case *ast.SelectorExpr: 306 // rewrite x.Trace to x._Trace (for Config.Trace) 307 if n.Sel.Name == "Trace" { 308 n.Sel.Name = "_Trace" 309 return false 310 } 311 } 312 return true 313 }) 314 } 315 316 // fixGlobalTypVarDecl changes the global Typ variable from an array to a slice 317 // (in types2 we use an array for efficiency, in go/types it's a slice and we 318 // cannot change that). 319 func fixGlobalTypVarDecl(f *ast.File) { 320 ast.Inspect(f, func(n ast.Node) bool { 321 switch n := n.(type) { 322 case *ast.ValueSpec: 323 // rewrite type Typ = [...]Type{...} to type Typ = []Type{...} 324 if len(n.Names) == 1 && n.Names[0].Name == "Typ" && len(n.Values) == 1 { 325 n.Values[0].(*ast.CompositeLit).Type.(*ast.ArrayType).Len = nil 326 return false 327 } 328 } 329 return true 330 }) 331 } 332 333 // fixSprintf adds an extra nil argument for the *token.FileSet parameter in sprintf calls. 334 func fixSprintf(f *ast.File) { 335 ast.Inspect(f, func(n ast.Node) bool { 336 switch n := n.(type) { 337 case *ast.CallExpr: 338 if fun, _ := n.Fun.(*ast.Ident); fun != nil && fun.Name == "sprintf" && len(n.Args) >= 4 /* ... args */ { 339 n.Args = insert(n.Args, 1, newIdent(n.Args[1].Pos(), "nil")) 340 return false 341 } 342 } 343 return true 344 }) 345 } 346 347 // newIdent returns a new identifier with the given position and name. 348 func newIdent(pos token.Pos, name string) *ast.Ident { 349 id := ast.NewIdent(name) 350 id.NamePos = pos 351 return id 352 } 353 354 // insert inserts x at list[at] and moves the remaining elements up. 355 func insert(list []ast.Expr, at int, x ast.Expr) []ast.Expr { 356 list = append(list, nil) 357 copy(list[at+1:], list[at:]) 358 list[at] = x 359 return list 360 }