github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/types/objectpath/objectpath_test.go (about)

     1  // Copyright 2018 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  package objectpath_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/importer"
    12  	"go/parser"
    13  	"go/token"
    14  	"go/types"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/powerman/golang-tools/go/buildutil"
    19  	"github.com/powerman/golang-tools/go/gcexportdata"
    20  	"github.com/powerman/golang-tools/go/loader"
    21  	"github.com/powerman/golang-tools/go/types/objectpath"
    22  )
    23  
    24  func TestPaths(t *testing.T) {
    25  	pkgs := map[string]map[string]string{
    26  		"b": {"b.go": `
    27  package b
    28  
    29  import "a"
    30  
    31  const C = a.Int(0)
    32  
    33  func F(a, b, c int, d a.T)
    34  
    35  type T struct{ A int; b int; a.T }
    36  
    37  func (T) M() *interface{ f() }
    38  
    39  type U T
    40  
    41  type A = struct{ x int }
    42  
    43  var V []*a.T
    44  
    45  type M map[struct{x int}]struct{y int}
    46  
    47  func unexportedFunc()
    48  type unexportedType struct{}
    49  
    50  type S struct{t struct{x int}}
    51  type R []struct{y int}
    52  type Q [2]struct{z int}
    53  `},
    54  		"a": {"a.go": `
    55  package a
    56  
    57  type Int int
    58  
    59  type T struct{x, y int}
    60  
    61  `},
    62  	}
    63  	paths := []pathTest{
    64  		// Good paths
    65  		{"b", "C", "const b.C a.Int", ""},
    66  		{"b", "F", "func b.F(a int, b int, c int, d a.T)", ""},
    67  		{"b", "F.PA0", "var a int", ""},
    68  		{"b", "F.PA1", "var b int", ""},
    69  		{"b", "F.PA2", "var c int", ""},
    70  		{"b", "F.PA3", "var d a.T", ""},
    71  		{"b", "T", "type b.T struct{A int; b int; a.T}", ""},
    72  		{"b", "T.O", "type b.T struct{A int; b int; a.T}", ""},
    73  		{"b", "T.UF0", "field A int", ""},
    74  		{"b", "T.UF1", "field b int", ""},
    75  		{"b", "T.UF2", "field T a.T", ""},
    76  		{"b", "U.UF2", "field T a.T", ""}, // U.U... are aliases for T.U...
    77  		{"b", "A", "type b.A = struct{x int}", ""},
    78  		{"b", "A.F0", "field x int", ""},
    79  		{"b", "V", "var b.V []*a.T", ""},
    80  		{"b", "M", "type b.M map[struct{x int}]struct{y int}", ""},
    81  		{"b", "M.UKF0", "field x int", ""},
    82  		{"b", "M.UEF0", "field y int", ""},
    83  		{"b", "T.M0", "func (b.T).M() *interface{f()}", ""}, // concrete method
    84  		{"b", "T.M0.RA0", "var  *interface{f()}", ""},       // parameter
    85  		{"b", "T.M0.RA0.EM0", "func (interface).f()", ""},   // interface method
    86  		{"b", "unexportedType", "type b.unexportedType struct{}", ""},
    87  		{"b", "S.UF0.F0", "field x int", ""},
    88  		{"b", "R.UEF0", "field y int", ""},
    89  		{"b", "Q.UEF0", "field z int", ""},
    90  		{"a", "T", "type a.T struct{x int; y int}", ""},
    91  		{"a", "T.UF0", "field x int", ""},
    92  
    93  		// Bad paths
    94  		{"b", "", "", "empty path"},
    95  		{"b", "missing", "", `package b does not contain "missing"`},
    96  		{"b", "F.U", "", "invalid path: ends with 'U', want [AFMO]"},
    97  		{"b", "F.PA3.O", "", "path denotes type a.T struct{x int; y int}, which belongs to a different package"},
    98  		{"b", "F.PA!", "", `invalid path: bad numeric operand "" for code 'A'`},
    99  		{"b", "F.PA3.UF0", "", "path denotes field x int, which belongs to a different package"},
   100  		{"b", "F.PA3.UF5", "", "field index 5 out of range [0-2)"},
   101  		{"b", "V.EE", "", "invalid path: ends with 'E', want [AFMO]"},
   102  		{"b", "F..O", "", "invalid path: unexpected '.' in type context"},
   103  		{"b", "T.OO", "", "invalid path: code 'O' in object context"},
   104  		{"b", "T.EO", "", "cannot apply 'E' to b.T (got *types.Named, want pointer, slice, array, chan or map)"},
   105  		{"b", "A.O", "", "cannot apply 'O' to struct{x int} (got *types.Struct, want named or type param)"},
   106  		{"b", "A.UF0", "", "cannot apply 'U' to struct{x int} (got *types.Struct, want named)"},
   107  		{"b", "M.UPO", "", "cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
   108  		{"b", "C.O", "", "path denotes type a.Int int, which belongs to a different package"},
   109  		{"b", "T.M9", "", "method index 9 out of range [0-1)"},
   110  		{"b", "M.UF0", "", "cannot apply 'F' to map[struct{x int}]struct{y int} (got *types.Map, want struct)"},
   111  		{"b", "V.KO", "", "cannot apply 'K' to []*a.T (got *types.Slice, want map)"},
   112  		{"b", "V.A4", "", "cannot apply 'A' to []*a.T (got *types.Slice, want tuple)"},
   113  		{"b", "V.RA0", "", "cannot apply 'R' to []*a.T (got *types.Slice, want signature)"},
   114  		{"b", "F.PA4", "", "tuple index 4 out of range [0-4)"},
   115  		{"b", "F.XO", "", "invalid path: unknown code 'X'"},
   116  	}
   117  	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
   118  	conf.Import("a")
   119  	conf.Import("b")
   120  	prog, err := conf.Load()
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	for _, test := range paths {
   126  		if err := testPath(prog, test); err != nil {
   127  			t.Error(err)
   128  		}
   129  	}
   130  
   131  	// bad objects
   132  	bInfo := prog.Imported["b"]
   133  	for _, test := range []struct {
   134  		obj     types.Object
   135  		wantErr string
   136  	}{
   137  		{types.Universe.Lookup("nil"), "predeclared nil has no path"},
   138  		{types.Universe.Lookup("len"), "predeclared builtin len has no path"},
   139  		{types.Universe.Lookup("int"), "predeclared type int has no path"},
   140  		{bInfo.Implicits[bInfo.Files[0].Imports[0]], "no path for package a"}, // import "a"
   141  		{bInfo.Pkg.Scope().Lookup("unexportedFunc"), "no path for non-exported func b.unexportedFunc()"},
   142  	} {
   143  		path, err := objectpath.For(test.obj)
   144  		if err == nil {
   145  			t.Errorf("Object(%s) = %q, want error", test.obj, path)
   146  			continue
   147  		}
   148  		if err.Error() != test.wantErr {
   149  			t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
   150  			continue
   151  		}
   152  	}
   153  }
   154  
   155  type pathTest struct {
   156  	pkg     string
   157  	path    objectpath.Path
   158  	wantobj string
   159  	wantErr string
   160  }
   161  
   162  func testPath(prog *loader.Program, test pathTest) error {
   163  	// We test objectpath by enumerating a set of paths
   164  	// and ensuring that Path(pkg, Object(pkg, path)) == path.
   165  	//
   166  	// It might seem more natural to invert the test:
   167  	// identify a set of objects and for each one,
   168  	// ensure that Object(pkg, Path(pkg, obj)) == obj.
   169  	// However, for most interesting test cases there is no
   170  	// easy way to identify the object short of applying
   171  	// a series of destructuring operations to pkg---which
   172  	// is essentially what objectpath.Object does.
   173  	// (We do a little of that when testing bad paths, below.)
   174  	//
   175  	// The downside is that the test depends on the path encoding.
   176  	// The upside is that the test exercises the encoding.
   177  
   178  	pkg := prog.Imported[test.pkg].Pkg
   179  	// check path -> object
   180  	obj, err := objectpath.Object(pkg, test.path)
   181  	if (test.wantErr != "") != (err != nil) {
   182  		return fmt.Errorf("Object(%s, %q) returned error %q, want %q", pkg.Path(), test.path, err, test.wantErr)
   183  	}
   184  	if test.wantErr != "" {
   185  		if got := stripSubscripts(err.Error()); got != test.wantErr {
   186  			return fmt.Errorf("Object(%s, %q) error was %q, want %q",
   187  				pkg.Path(), test.path, got, test.wantErr)
   188  		}
   189  		return nil
   190  	}
   191  	// Inv: err == nil
   192  
   193  	if objString := stripSubscripts(obj.String()); objString != test.wantobj {
   194  		return fmt.Errorf("Object(%s, %q) = %s, want %s", pkg.Path(), test.path, objString, test.wantobj)
   195  	}
   196  	if obj.Pkg() != pkg {
   197  		return fmt.Errorf("Object(%s, %q) = %v, which belongs to package %s",
   198  			pkg.Path(), test.path, obj, obj.Pkg().Path())
   199  	}
   200  
   201  	// check object -> path
   202  	path2, err := objectpath.For(obj)
   203  	if err != nil {
   204  		return fmt.Errorf("For(%v) failed: %v, want %q", obj, err, test.path)
   205  	}
   206  	// We do not require that test.path == path2. Aliases are legal.
   207  	// But we do require that Object(path2) finds the same object.
   208  	obj2, err := objectpath.Object(pkg, path2)
   209  	if err != nil {
   210  		return fmt.Errorf("Object(%s, %q) failed: %v (roundtrip from %q)", pkg.Path(), path2, err, test.path)
   211  	}
   212  	if obj2 != obj {
   213  		return fmt.Errorf("Object(%s, For(obj)) != obj: got %s, obj is %s (path1=%q, path2=%q)", pkg.Path(), obj2, obj, test.path, path2)
   214  	}
   215  	return nil
   216  }
   217  
   218  // stripSubscripts removes type parameter id subscripts.
   219  //
   220  // TODO(rfindley): remove this function once subscripts are removed from the
   221  // type parameter type string.
   222  func stripSubscripts(s string) string {
   223  	var runes []rune
   224  	for _, r := range s {
   225  		// For debugging/uniqueness purposes, TypeString on a type parameter adds a
   226  		// subscript corresponding to the type parameter's unique id. This is going
   227  		// to be removed, but in the meantime we skip the subscript runes to get a
   228  		// deterministic output.
   229  		if '₀' <= r && r < '₀'+10 {
   230  			continue // trim type parameter subscripts
   231  		}
   232  		runes = append(runes, r)
   233  	}
   234  	return string(runes)
   235  }
   236  
   237  // TestSourceAndExportData uses objectpath to compute a correspondence
   238  // of objects between two versions of the same package, one loaded from
   239  // source, the other from export data.
   240  func TestSourceAndExportData(t *testing.T) {
   241  	const src = `
   242  package p
   243  
   244  type I int
   245  
   246  func (I) F() *struct{ X, Y int } {
   247  	return nil
   248  }
   249  
   250  type Foo interface {
   251  	Method() (string, func(int) struct{ X int })
   252  }
   253  
   254  var X chan struct{ Z int }
   255  var Z map[string]struct{ A int }
   256  `
   257  
   258  	// Parse source file and type-check it as a package, "src".
   259  	fset := token.NewFileSet()
   260  	f, err := parser.ParseFile(fset, "src.go", src, 0)
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  	conf := types.Config{Importer: importer.For("source", nil)}
   265  	info := &types.Info{
   266  		Defs: make(map[*ast.Ident]types.Object),
   267  	}
   268  	srcpkg, err := conf.Check("src/p", fset, []*ast.File{f}, info)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	// Export binary export data then reload it as a new package, "bin".
   274  	var buf bytes.Buffer
   275  	if err := gcexportdata.Write(&buf, fset, srcpkg); err != nil {
   276  		t.Fatal(err)
   277  	}
   278  
   279  	imports := make(map[string]*types.Package)
   280  	binpkg, err := gcexportdata.Read(&buf, fset, imports, "bin/p")
   281  	if err != nil {
   282  		t.Fatal(err)
   283  	}
   284  
   285  	// Now find the correspondences between them.
   286  	for _, srcobj := range info.Defs {
   287  		if srcobj == nil {
   288  			continue // e.g. package declaration
   289  		}
   290  		if _, ok := srcobj.(*types.PkgName); ok {
   291  			continue // PkgName has no objectpath
   292  		}
   293  
   294  		path, err := objectpath.For(srcobj)
   295  		if err != nil {
   296  			t.Errorf("For(%v): %v", srcobj, err)
   297  			continue
   298  		}
   299  		binobj, err := objectpath.Object(binpkg, path)
   300  		if err != nil {
   301  			t.Errorf("Object(%s, %q): %v", binpkg.Path(), path, err)
   302  			continue
   303  		}
   304  
   305  		// Check the object strings match.
   306  		// (We can't check that types are identical because the
   307  		// objects belong to different type-checker realms.)
   308  		srcstr := objectString(srcobj)
   309  		binstr := objectString(binobj)
   310  		if srcstr != binstr {
   311  			t.Errorf("ObjectStrings do not match: Object(For(%q)) = %s, want %s",
   312  				path, srcstr, binstr)
   313  			continue
   314  		}
   315  	}
   316  }
   317  
   318  func objectString(obj types.Object) string {
   319  	s := types.ObjectString(obj, (*types.Package).Name)
   320  
   321  	// The printing of interface methods changed in go1.11.
   322  	// This work-around makes the specific test pass with earlier versions.
   323  	s = strings.Replace(s, "func (interface).Method", "func (p.Foo).Method", -1)
   324  
   325  	return s
   326  }
   327  
   328  // TestOrdering uses objectpath over two Named types with the same method
   329  // names but in a different source order and checks that objectpath is the
   330  // same for methods with the same name.
   331  func TestOrdering(t *testing.T) {
   332  	pkgs := map[string]map[string]string{
   333  		"p": {"p.go": `
   334  package p
   335  
   336  type T struct{ A int }
   337  
   338  func (T) M() { }
   339  func (T) N() { }
   340  func (T) X() { }
   341  func (T) Y() { }
   342  `},
   343  		"q": {"q.go": `
   344  package q
   345  
   346  type T struct{ A int }
   347  
   348  func (T) N() { }
   349  func (T) M() { }
   350  func (T) Y() { }
   351  func (T) X() { }
   352  `}}
   353  	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
   354  	conf.Import("p")
   355  	conf.Import("q")
   356  	prog, err := conf.Load()
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	p := prog.Imported["p"].Pkg
   361  	q := prog.Imported["q"].Pkg
   362  
   363  	// From here, the objectpaths generated for p and q should be the
   364  	// same. If they are not, then we are generating an ordering that is
   365  	// dependent on the declaration of the types within the file.
   366  	for _, test := range []struct {
   367  		path objectpath.Path
   368  	}{
   369  		{"T.M0"},
   370  		{"T.M1"},
   371  		{"T.M2"},
   372  		{"T.M3"},
   373  	} {
   374  		pobj, err := objectpath.Object(p, test.path)
   375  		if err != nil {
   376  			t.Errorf("Object(%s) failed in a1: %v", test.path, err)
   377  			continue
   378  		}
   379  		qobj, err := objectpath.Object(q, test.path)
   380  		if err != nil {
   381  			t.Errorf("Object(%s) failed in a2: %v", test.path, err)
   382  			continue
   383  		}
   384  		if pobj.Name() != pobj.Name() {
   385  			t.Errorf("Objects(%s) not equal, got a1 = %v, a2 = %v", test.path, pobj.Name(), qobj.Name())
   386  		}
   387  	}
   388  }