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

View as plain text