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  	"fmt"
    14  	"go/ast"
    15  	"go/format"
    16  	"go/parser"
    17  	"go/token"
    18  	"internal/diff"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"strings"
    23  	"testing"
    24  )
    25  
    26  var filesToWrite = flag.String("write", "", `go/types files to generate, or "all" for all files`)
    27  
    28  const (
    29  	srcDir = "/src/cmd/compile/internal/types2/"
    30  	dstDir = "/src/go/types/"
    31  )
    32  
    33  // TestGenerate verifies that generated files in go/types match their types2
    34  // counterpart. If -write is set, this test actually writes the expected
    35  // content to go/types; otherwise, it just compares with the existing content.
    36  func TestGenerate(t *testing.T) {
    37  	// If filesToWrite is set, write the generated content to disk.
    38  	// In the special case of "all", write all files in filemap.
    39  	write := *filesToWrite != ""
    40  	var files []string // files to process
    41  	if *filesToWrite != "" && *filesToWrite != "all" {
    42  		files = strings.Split(*filesToWrite, ",")
    43  	} else {
    44  		for file := range filemap {
    45  			files = append(files, file)
    46  		}
    47  	}
    48  
    49  	for _, filename := range files {
    50  		generate(t, filename, write)
    51  	}
    52  }
    53  
    54  func generate(t *testing.T, filename string, write bool) {
    55  	// parse src (cmd/compile/internal/types2)
    56  	srcFilename := filepath.FromSlash(runtime.GOROOT() + srcDir + filename)
    57  	file, err := parser.ParseFile(testFSet, srcFilename, nil, parser.ParseComments|parser.SkipObjectResolution)
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  
    62  	// fix package name
    63  	file.Name.Name = strings.ReplaceAll(file.Name.Name, "types2", "types")
    64  
    65  	// rewrite AST as needed
    66  	if action := filemap[filename]; action != nil {
    67  		action(file)
    68  	}
    69  
    70  	// format AST
    71  	var buf bytes.Buffer
    72  	rel, _ := filepath.Rel(dstDir, srcDir)
    73  	fmt.Fprintf(&buf, "// Code generated by \"go test -run=Generate -write=all\"; DO NOT EDIT.\n")
    74  	fmt.Fprintf(&buf, "// Source: %s/%s\n\n", filepath.ToSlash(rel), filename)
    75  	if err := format.Node(&buf, testFSet, file); err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	generatedContent := buf.Bytes()
    79  
    80  	// read dst (go/types)
    81  	dstFilename := filepath.FromSlash(runtime.GOROOT() + dstDir + filename)
    82  	onDiskContent, err := os.ReadFile(dstFilename)
    83  	if err != nil {
    84  		t.Fatalf("reading %q: %v", filename, err)
    85  	}
    86  
    87  	// compare on-disk dst with buffer generated from src.
    88  	if d := diff.Diff(filename+" (on disk in "+dstDir+")", onDiskContent, filename+" (generated from "+srcDir+")", generatedContent); d != nil {
    89  		if write {
    90  			t.Logf("applying change:\n%s", d)
    91  			if err := os.WriteFile(dstFilename, generatedContent, 0o644); err != nil {
    92  				t.Fatalf("writing %q: %v", filename, err)
    93  			}
    94  		} else {
    95  			t.Errorf("file on disk in %s is stale:\n%s", dstDir, d)
    96  		}
    97  	}
    98  }
    99  
   100  type action func(in *ast.File)
   101  
   102  var filemap = map[string]action{
   103  	"alias.go": fixTokenPos,
   104  	"alias_test.go": func(f *ast.File) {
   105  		renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`)
   106  		renameIdents(f, "types2->types")
   107  	},
   108  	"assignments.go": func(f *ast.File) {
   109  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   110  		renameSelectorExprs(f, "syntax.Name->ast.Ident", "ident.Value->ident.Name", "ast.Pos->token.Pos") // must happen before renaming identifiers
   111  		renameIdents(f, "syntax->ast", "poser->positioner", "nopos->noposn")
   112  	},
   113  	"array.go":          nil,
   114  	"api_predicates.go": nil,
   115  	"basic.go":          nil,
   116  	"builtins.go": func(f *ast.File) {
   117  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   118  		renameIdents(f, "syntax->ast")
   119  		renameSelectors(f, "ArgList->Args")
   120  		fixSelValue(f)
   121  		fixAtPosCall(f)
   122  	},
   123  	"builtins_test.go": func(f *ast.File) {
   124  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`, `"cmd/compile/internal/types2"->"go/types"`)
   125  		renameSelectorExprs(f, "syntax.Name->ast.Ident", "p.Value->p.Name") // must happen before renaming identifiers
   126  		renameIdents(f, "syntax->ast")
   127  	},
   128  	"chan.go":         nil,
   129  	"const.go":        fixTokenPos,
   130  	"context.go":      nil,
   131  	"context_test.go": nil,
   132  	"conversions.go":  nil,
   133  	"cycles.go": func(f *ast.File) {
   134  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   135  		renameSelectorExprs(f, "syntax.Name->ast.Ident", "rhs.Value->rhs.Name")
   136  		renameSelectors(f, "Trace->_Trace")
   137  	},
   138  	"errorcalls_test.go": func(f *ast.File) {
   139  		insertImportPath(f, `"go/token"`)
   140  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   141  		renameSelectorExprs(f, "syntax.StringLit->token.STRING", "syntax.Name->ast.Ident", "n.Value->n.Name")
   142  		renameIdents(f, "syntax->ast")
   143  		renameSelectors(f, "ArgList->Args")
   144  		fixFSet(f)
   145  	},
   146  	"errors_test.go": func(f *ast.File) { renameIdents(f, "nopos->noposn") },
   147  	"errsupport.go":  nil,
   148  	"gccgosizes.go":  nil,
   149  	"gcsizes.go": func(f *ast.File) {
   150  		renameIdents(f, "IsSyncAtomicAlign64->_IsSyncAtomicAlign64", "IsSyncAtomicAlign128->_IsSyncAtomicAlign128")
   151  	},
   152  	"hilbert_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`) },
   153  	"infer.go":        func(f *ast.File) { fixTokenPos(f); fixInferSig(f) },
   154  	"initorder.go":    nil,
   155  	// "initorder.go": fixErrErrorfCall, // disabled for now due to unresolved error_ use implications for gopls
   156  	"instantiate.go":      func(f *ast.File) { fixTokenPos(f); fixCheckErrorfCall(f); fixSprintf(f) },
   157  	"instantiate_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`) },
   158  	"literals.go": func(f *ast.File) {
   159  		insertImportPath(f, `"go/token"`)
   160  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   161  		renameSelectorExprs(f,
   162  			"syntax.IntLit->token.INT", "syntax.FloatLit->token.FLOAT", "syntax.ImagLit->token.IMAG",
   163  			"syntax.Name->ast.Ident", "key.Value->key.Name", "atyp.Elem->atyp.Elt") // must happen before renaming identifiers
   164  		renameIdents(f, "syntax->ast")
   165  		renameSelectors(f, "ElemList->Elts")
   166  	},
   167  	"lookup.go": fixTokenPos,
   168  	"lookup_test.go": func(f *ast.File) {
   169  		renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`)
   170  		fixFSet(f)
   171  	},
   172  	"main_test.go": nil,
   173  	"map.go":       nil,
   174  	"mono.go": func(f *ast.File) {
   175  		fixTokenPos(f)
   176  		insertImportPath(f, `"go/ast"`)
   177  		renameSelectorExprs(f, "syntax.Expr->ast.Expr")
   178  	},
   179  	"mono_test.go": func(f *ast.File) {
   180  		renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`)
   181  		renameIdents(f, "types2->types")
   182  		fixFSet(f)
   183  	},
   184  	"named.go":       func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
   185  	"object.go":      func(f *ast.File) { fixTokenPos(f); renameIdents(f, "NewTypeNameLazy->_NewTypeNameLazy") },
   186  	"object_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`) },
   187  	"objset.go":      nil,
   188  	"operand.go": func(f *ast.File) {
   189  		insertImportPath(f, `"go/token"`)
   190  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   191  		renameSelectorExprs(f,
   192  			"syntax.Pos->token.Pos", "syntax.LitKind->token.Token",
   193  			"syntax.IntLit->token.INT", "syntax.FloatLit->token.FLOAT",
   194  			"syntax.ImagLit->token.IMAG", "syntax.RuneLit->token.CHAR",
   195  			"syntax.StringLit->token.STRING") // must happen before renaming identifiers
   196  		renameIdents(f, "syntax->ast")
   197  	},
   198  	"package.go":    nil,
   199  	"pointer.go":    nil,
   200  	"predicates.go": nil,
   201  	"range.go": func(f *ast.File) {
   202  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   203  		renameSelectorExprs(f, "syntax.Name->ast.Ident", "syntax.ForStmt->ast.RangeStmt", "ident.Value->ident.Name") // must happen before renaming identifiers
   204  		renameIdents(f, "syntax->ast", "poser->positioner")
   205  	},
   206  	"recording.go": func(f *ast.File) {
   207  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   208  		renameSelectorExprs(f, "syntax.Name->ast.Ident") // must happen before renaming identifiers
   209  		renameIdents(f, "syntax->ast")
   210  		fixAtPosCall(f)
   211  	},
   212  	"scope.go":       func(f *ast.File) { fixTokenPos(f); renameIdents(f, "InsertLazy->_InsertLazy") },
   213  	"scope_test.go":  func(f *ast.File) { fixTokenPos(f) },
   214  	"selection.go":   nil,
   215  	"sizeof_test.go": nil,
   216  	"sizes.go": func(f *ast.File) {
   217  		renameIdents(f, "IsSyncAtomicAlign64->_IsSyncAtomicAlign64", "IsSyncAtomicAlign128->_IsSyncAtomicAlign128")
   218  	},
   219  	"sizes_test.go": func(f *ast.File) {
   220  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`, `"cmd/compile/internal/types2"->"go/types"`)
   221  		renameSelectorExprs(f, "syntax.Expr->ast.Expr")
   222  		renameIdents(f, "types2->types")
   223  		fixFSet(f)
   224  	},
   225  	"slice.go":           nil,
   226  	"subst.go":           func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
   227  	"termlist.go":        nil,
   228  	"termlist_test.go":   nil,
   229  	"trie.go":            nil,
   230  	"trie_test.go":       nil,
   231  	"tuple.go":           nil,
   232  	"typelists.go":       nil,
   233  	"typeset.go":         func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
   234  	"typeset_test.go":    func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`) },
   235  	"typeparam.go":       nil,
   236  	"typeterm_test.go":   nil,
   237  	"typeterm.go":        nil,
   238  	"typestring.go":      nil,
   239  	"typestring_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"->"go/types"`) },
   240  	"under.go":           nil,
   241  	"unify.go":           fixSprintf,
   242  	"union.go": func(f *ast.File) {
   243  		renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
   244  		renameSelectorExprs(f, "syntax.Expr->ast.Expr")
   245  	},
   246  	"universe.go":  fixGlobalTypVarDecl,
   247  	"util_test.go": fixTokenPos,
   248  	"validtype.go": func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
   249  	"version.go":   func(f *ast.File) { renameIdents(f, "poser->positioner") },
   250  }
   251  
   252  // TODO(gri) We should be able to make these rewriters more configurable/composable.
   253  //           For now this is a good starting point.
   254  
   255  // A renameMap maps old strings to new strings.
   256  type renameMap map[string]string
   257  
   258  // makeRenameMap returns a renameMap populates from renames entries of the form "from->to".
   259  func makeRenameMap(renames ...string) renameMap {
   260  	m := make(renameMap)
   261  	for _, r := range renames {
   262  		s := strings.Split(r, "->")
   263  		if len(s) != 2 {
   264  			panic("invalid rename entry: " + r)
   265  		}
   266  		m[s[0]] = s[1]
   267  	}
   268  	return m
   269  }
   270  
   271  // rename renames the given string s if a corresponding rename exists in m.
   272  func (m renameMap) rename(s *string) {
   273  	if r, ok := m[*s]; ok {
   274  		*s = r
   275  	}
   276  }
   277  
   278  // renameSel renames a selector expression of the form a.x to b.x (where a, b are identifiers)
   279  // if m contains the ("a.x" : "b.y") key-value pair.
   280  func (m renameMap) renameSel(n *ast.SelectorExpr) {
   281  	if a, _ := n.X.(*ast.Ident); a != nil {
   282  		a_x := a.Name + "." + n.Sel.Name
   283  		if r, ok := m[a_x]; ok {
   284  			b_y := strings.Split(r, ".")
   285  			if len(b_y) != 2 {
   286  				panic("invalid selector expression: " + r)
   287  			}
   288  			a.Name = b_y[0]
   289  			n.Sel.Name = b_y[1]
   290  		}
   291  	}
   292  }
   293  
   294  // renameIdents renames identifiers: each renames entry is of the form "from->to".
   295  // Note: This doesn't change the use of the identifiers in comments.
   296  func renameIdents(f *ast.File, renames ...string) {
   297  	m := makeRenameMap(renames...)
   298  	ast.Inspect(f, func(n ast.Node) bool {
   299  		switch n := n.(type) {
   300  		case *ast.Ident:
   301  			m.rename(&n.Name)
   302  			return false
   303  		}
   304  		return true
   305  	})
   306  }
   307  
   308  // renameSelectors is like renameIdents but only looks at selectors.
   309  func renameSelectors(f *ast.File, renames ...string) {
   310  	m := makeRenameMap(renames...)
   311  	ast.Inspect(f, func(n ast.Node) bool {
   312  		switch n := n.(type) {
   313  		case *ast.SelectorExpr:
   314  			m.rename(&n.Sel.Name)
   315  			return false
   316  		}
   317  		return true
   318  	})
   319  
   320  }
   321  
   322  // renameSelectorExprs is like renameIdents but only looks at selector expressions.
   323  // Each renames entry must be of the form "x.a->y.b".
   324  func renameSelectorExprs(f *ast.File, renames ...string) {
   325  	m := makeRenameMap(renames...)
   326  	ast.Inspect(f, func(n ast.Node) bool {
   327  		switch n := n.(type) {
   328  		case *ast.SelectorExpr:
   329  			m.renameSel(n)
   330  			return false
   331  		}
   332  		return true
   333  	})
   334  }
   335  
   336  // renameImportPath is like renameIdents but renames import paths.
   337  func renameImportPath(f *ast.File, renames ...string) {
   338  	m := makeRenameMap(renames...)
   339  	ast.Inspect(f, func(n ast.Node) bool {
   340  		switch n := n.(type) {
   341  		case *ast.ImportSpec:
   342  			if n.Path.Kind != token.STRING {
   343  				panic("invalid import path")
   344  			}
   345  			m.rename(&n.Path.Value)
   346  			return false
   347  		}
   348  		return true
   349  	})
   350  }
   351  
   352  // insertImportPath inserts the given import path.
   353  // There must be at least one import declaration present already.
   354  func insertImportPath(f *ast.File, path string) {
   355  	for _, d := range f.Decls {
   356  		if g, _ := d.(*ast.GenDecl); g != nil && g.Tok == token.IMPORT {
   357  			g.Specs = append(g.Specs, &ast.ImportSpec{Path: &ast.BasicLit{ValuePos: g.End(), Kind: token.STRING, Value: path}})
   358  			return
   359  		}
   360  	}
   361  	panic("no import declaration present")
   362  }
   363  
   364  // fixTokenPos changes imports of "cmd/compile/internal/syntax" to "go/token",
   365  // uses of syntax.Pos to token.Pos, and calls to x.IsKnown() to x.IsValid().
   366  func fixTokenPos(f *ast.File) {
   367  	m := makeRenameMap(`"cmd/compile/internal/syntax"->"go/token"`, "syntax.Pos->token.Pos", "IsKnown->IsValid")
   368  	ast.Inspect(f, func(n ast.Node) bool {
   369  		switch n := n.(type) {
   370  		case *ast.ImportSpec:
   371  			// rewrite import path "cmd/compile/internal/syntax" to "go/token"
   372  			if n.Path.Kind != token.STRING {
   373  				panic("invalid import path")
   374  			}
   375  			m.rename(&n.Path.Value)
   376  			return false
   377  		case *ast.SelectorExpr:
   378  			// rewrite syntax.Pos to token.Pos
   379  			m.renameSel(n)
   380  		case *ast.CallExpr:
   381  			// rewrite x.IsKnown() to x.IsValid()
   382  			if fun, _ := n.Fun.(*ast.SelectorExpr); fun != nil && len(n.Args) == 0 {
   383  				m.rename(&fun.Sel.Name)
   384  				return false
   385  			}
   386  		}
   387  		return true
   388  	})
   389  }
   390  
   391  // fixSelValue updates the selector x.Sel.Value to x.Sel.Name.
   392  func fixSelValue(f *ast.File) {
   393  	ast.Inspect(f, func(n ast.Node) bool {
   394  		switch n := n.(type) {
   395  		case *ast.SelectorExpr:
   396  			if n.Sel.Name == "Value" {
   397  				if selx, _ := n.X.(*ast.SelectorExpr); selx != nil && selx.Sel.Name == "Sel" {
   398  					n.Sel.Name = "Name"
   399  					return false
   400  				}
   401  			}
   402  		}
   403  		return true
   404  	})
   405  }
   406  
   407  // fixInferSig updates the Checker.infer signature to use a positioner instead of a token.Position
   408  // as first argument, renames the argument from "pos" to "posn", and updates a few internal uses of
   409  // "pos" to "posn" and "posn.Pos()" respectively.
   410  func fixInferSig(f *ast.File) {
   411  	ast.Inspect(f, func(n ast.Node) bool {
   412  		switch n := n.(type) {
   413  		case *ast.FuncDecl:
   414  			if n.Name.Name == "infer" {
   415  				// rewrite (pos token.Pos, ...) to (posn positioner, ...)
   416  				par := n.Type.Params.List[0]
   417  				if len(par.Names) == 1 && par.Names[0].Name == "pos" {
   418  					par.Names[0] = newIdent(par.Names[0].Pos(), "posn")
   419  					par.Type = newIdent(par.Type.Pos(), "positioner")
   420  					return true
   421  				}
   422  			}
   423  		case *ast.CallExpr:
   424  			if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil {
   425  				switch selx.Sel.Name {
   426  				case "renameTParams":
   427  					// rewrite check.renameTParams(pos, ... ) to check.renameTParams(posn.Pos(), ... )
   428  					if isIdent(n.Args[0], "pos") {
   429  						pos := n.Args[0].Pos()
   430  						fun := &ast.SelectorExpr{X: newIdent(pos, "posn"), Sel: newIdent(pos, "Pos")}
   431  						arg := &ast.CallExpr{Fun: fun, Lparen: pos, Args: nil, Ellipsis: token.NoPos, Rparen: pos}
   432  						n.Args[0] = arg
   433  						return false
   434  					}
   435  				case "addf":
   436  					// rewrite err.addf(pos, ...) to err.addf(posn, ...)
   437  					if isIdent(n.Args[0], "pos") {
   438  						pos := n.Args[0].Pos()
   439  						arg := newIdent(pos, "posn")
   440  						n.Args[0] = arg
   441  						return false
   442  					}
   443  				case "allowVersion":
   444  					// rewrite check.allowVersion(pos, ...) to check.allowVersion(posn, ...)
   445  					if isIdent(n.Args[0], "pos") {
   446  						pos := n.Args[0].Pos()
   447  						arg := newIdent(pos, "posn")
   448  						n.Args[0] = arg
   449  						return false
   450  					}
   451  				}
   452  			}
   453  		}
   454  		return true
   455  	})
   456  }
   457  
   458  // fixAtPosCall updates calls of the form atPos(x) to x.Pos() in argument lists of (check).dump calls.
   459  // TODO(gri) can we avoid this and just use atPos consistently in go/types and types2?
   460  func fixAtPosCall(f *ast.File) {
   461  	ast.Inspect(f, func(n ast.Node) bool {
   462  		switch n := n.(type) {
   463  		case *ast.CallExpr:
   464  			if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil && selx.Sel.Name == "dump" {
   465  				for i, arg := range n.Args {
   466  					if call, _ := arg.(*ast.CallExpr); call != nil {
   467  						// rewrite xxx.dump(..., atPos(x), ...) to xxx.dump(..., x.Pos(), ...)
   468  						if isIdent(call.Fun, "atPos") {
   469  							pos := call.Args[0].Pos()
   470  							fun := &ast.SelectorExpr{X: call.Args[0], Sel: newIdent(pos, "Pos")}
   471  							n.Args[i] = &ast.CallExpr{Fun: fun, Lparen: pos, Rparen: pos}
   472  							return false
   473  						}
   474  					}
   475  				}
   476  			}
   477  		}
   478  		return true
   479  	})
   480  }
   481  
   482  // fixErrErrorfCall updates calls of the form err.addf(obj, ...) to err.addf(obj.Pos(), ...).
   483  func fixErrErrorfCall(f *ast.File) {
   484  	ast.Inspect(f, func(n ast.Node) bool {
   485  		switch n := n.(type) {
   486  		case *ast.CallExpr:
   487  			if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil {
   488  				if isIdent(selx.X, "err") {
   489  					switch selx.Sel.Name {
   490  					case "errorf":
   491  						// rewrite err.addf(obj, ... ) to err.addf(obj.Pos(), ... )
   492  						if ident, _ := n.Args[0].(*ast.Ident); ident != nil && ident.Name == "obj" {
   493  							pos := n.Args[0].Pos()
   494  							fun := &ast.SelectorExpr{X: ident, Sel: newIdent(pos, "Pos")}
   495  							n.Args[0] = &ast.CallExpr{Fun: fun, Lparen: pos, Rparen: pos}
   496  							return false
   497  						}
   498  					}
   499  				}
   500  			}
   501  		}
   502  		return true
   503  	})
   504  }
   505  
   506  // fixCheckErrorfCall updates calls of the form check.errorf(pos, ...) to check.errorf(atPos(pos), ...).
   507  func fixCheckErrorfCall(f *ast.File) {
   508  	ast.Inspect(f, func(n ast.Node) bool {
   509  		switch n := n.(type) {
   510  		case *ast.CallExpr:
   511  			if selx, _ := n.Fun.(*ast.SelectorExpr); selx != nil {
   512  				if isIdent(selx.X, "check") {
   513  					switch selx.Sel.Name {
   514  					case "errorf":
   515  						// rewrite check.errorf(pos, ... ) to check.errorf(atPos(pos), ... )
   516  						if ident := asIdent(n.Args[0], "pos"); ident != nil {
   517  							pos := n.Args[0].Pos()
   518  							fun := newIdent(pos, "atPos")
   519  							n.Args[0] = &ast.CallExpr{Fun: fun, Lparen: pos, Args: []ast.Expr{ident}, Rparen: pos}
   520  							return false
   521  						}
   522  					}
   523  				}
   524  			}
   525  		}
   526  		return true
   527  	})
   528  }
   529  
   530  // fixGlobalTypVarDecl changes the global Typ variable from an array to a slice
   531  // (in types2 we use an array for efficiency, in go/types it's a slice and we
   532  // cannot change that).
   533  func fixGlobalTypVarDecl(f *ast.File) {
   534  	ast.Inspect(f, func(n ast.Node) bool {
   535  		switch n := n.(type) {
   536  		case *ast.ValueSpec:
   537  			// rewrite type Typ = [...]Type{...} to type Typ = []Type{...}
   538  			if len(n.Names) == 1 && n.Names[0].Name == "Typ" && len(n.Values) == 1 {
   539  				n.Values[0].(*ast.CompositeLit).Type.(*ast.ArrayType).Len = nil
   540  				return false
   541  			}
   542  		}
   543  		return true
   544  	})
   545  }
   546  
   547  // fixSprintf adds an extra nil argument for the *token.FileSet parameter in sprintf calls.
   548  func fixSprintf(f *ast.File) {
   549  	ast.Inspect(f, func(n ast.Node) bool {
   550  		switch n := n.(type) {
   551  		case *ast.CallExpr:
   552  			if isIdent(n.Fun, "sprintf") && len(n.Args) >= 4 /* ... args */ {
   553  				n.Args = insert(n.Args, 1, newIdent(n.Args[1].Pos(), "nil"))
   554  				return false
   555  			}
   556  		}
   557  		return true
   558  	})
   559  }
   560  
   561  // fixFSet inserts the testFSet argument into calls to defaultImporter, pkgFiles,
   562  // mustParse (at index 0), and Check, NewChecker (at index 1), and wraps x.Pos()
   563  // arguments in Errorf calls with testFSet.Position(x.Pos()).
   564  func fixFSet(f *ast.File) {
   565  	newFSet := func(pos token.Pos) *ast.Ident { return newIdent(pos, "testFSet") }
   566  	ast.Inspect(f, func(n ast.Node) bool {
   567  		switch n := n.(type) {
   568  		case *ast.CallExpr:
   569  			switch {
   570  			case isIdent(n.Fun, "defaultImporter"), isIdent(n.Fun, "pkgFiles"), isIdent(n.Fun, "mustParse"):
   571  				pos := n.Lparen
   572  				n.Args = insert(n.Args, 0, newFSet(pos))
   573  				return false
   574  			case isIdent(n.Fun, "NewChecker"):
   575  				pos := n.Args[0].Pos()
   576  				n.Args = insert(n.Args, 1, newFSet(pos))
   577  				return false
   578  			}
   579  			if sel, _ := n.Fun.(*ast.SelectorExpr); sel != nil {
   580  				switch sel.Sel.Name {
   581  				case "Check":
   582  					pos := n.Args[0].Pos()
   583  					n.Args = insert(n.Args, 1, newFSet(pos))
   584  					return false
   585  				case "Errorf":
   586  					for i, arg := range n.Args {
   587  						if call, _ := arg.(*ast.CallExpr); call != nil && len(call.Args) == 0 {
   588  							if sel, _ := call.Fun.(*ast.SelectorExpr); sel != nil && sel.Sel.Name == "Pos" {
   589  								pos := call.Pos()
   590  								fun := &ast.SelectorExpr{X: newFSet(pos), Sel: newIdent(pos, "Position")}
   591  								n.Args[i] = &ast.CallExpr{Fun: fun, Lparen: pos, Args: []ast.Expr{call}, Rparen: pos}
   592  							}
   593  						}
   594  					}
   595  				}
   596  			}
   597  		}
   598  		return true
   599  	})
   600  }
   601  
   602  // asIdent returns x as *ast.Ident if it is an identifier with the given name.
   603  func asIdent(x ast.Node, name string) *ast.Ident {
   604  	if ident, _ := x.(*ast.Ident); ident != nil && ident.Name == name {
   605  		return ident
   606  	}
   607  	return nil
   608  }
   609  
   610  // isIdent reports whether x is an identifier with the given name.
   611  func isIdent(x ast.Node, name string) bool {
   612  	return asIdent(x, name) != nil
   613  }
   614  
   615  // newIdent returns a new identifier with the given position and name.
   616  func newIdent(pos token.Pos, name string) *ast.Ident {
   617  	id := ast.NewIdent(name)
   618  	id.NamePos = pos
   619  	return id
   620  }
   621  
   622  // insert inserts x at list[at] and moves the remaining elements up.
   623  func insert(list []ast.Expr, at int, x ast.Expr) []ast.Expr {
   624  	list = append(list, nil)
   625  	copy(list[at+1:], list[at:])
   626  	list[at] = x
   627  	return list
   628  }
   629  

View as plain text